Merge remote-tracking branch 'upstream/master' into anchor-part3
# Conflicts: # Tests/test_imagefontctl.py
|
@ -33,7 +33,8 @@ pip install pyroma
|
||||||
pip install test-image-results
|
pip install test-image-results
|
||||||
pip install numpy
|
pip install numpy
|
||||||
|
|
||||||
# TODO Remove when 3.9 includes setuptools 49.3.2+:
|
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
||||||
|
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
||||||
if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
||||||
|
|
||||||
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
|
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
|
8
.github/workflows/test-windows.yml
vendored
|
@ -63,9 +63,9 @@ jobs:
|
||||||
- name: pip install wheel pytest pytest-cov
|
- name: pip install wheel pytest pytest-cov
|
||||||
run: python -m pip install wheel pytest pytest-cov
|
run: python -m pip install wheel pytest pytest-cov
|
||||||
|
|
||||||
# TODO Remove when 3.9 includes setuptools 49.3.2+:
|
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
||||||
- name: Upgrade setuptools
|
- name: Upgrade setuptools
|
||||||
if: "contains(matrix.python-version, '3.9')"
|
if: "contains(matrix.python-version, '3.8') || contains(matrix.python-version, '3.9')"
|
||||||
run: python -m pip install -U "setuptools>=49.3.2"
|
run: python -m pip install -U "setuptools>=49.3.2"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
@ -105,6 +105,10 @@ jobs:
|
||||||
- name: Build dependencies / WebP
|
- name: Build dependencies / WebP
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
||||||
|
# for FreeType CBDT font support
|
||||||
|
- name: Build dependencies / libpng
|
||||||
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
|
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
||||||
- name: Build dependencies / FreeType
|
- name: Build dependencies / FreeType
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_freetype.cmd"
|
run: "& winbuild\\build\\build_dep_freetype.cmd"
|
||||||
|
|
BIN
Tests/fonts/BungeeColor-Regular_colr_Windows.ttf
Normal file
BIN
Tests/fonts/DejaVuSans-24-1-stripped.ttf
Normal file
BIN
Tests/fonts/DejaVuSans-24-2-stripped.ttf
Normal file
BIN
Tests/fonts/DejaVuSans-24-4-stripped.ttf
Normal file
BIN
Tests/fonts/DejaVuSans-24-8-stripped.ttf
Normal file
|
@ -2,15 +2,20 @@
|
||||||
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
|
||||||
ter-x20b.pcf, from http://terminus-font.sourceforge.net/
|
ter-x20b.pcf, from http://terminus-font.sourceforge.net/
|
||||||
|
BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee
|
||||||
|
|
||||||
All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
|
All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
|
||||||
|
|
||||||
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
|
||||||
|
|
||||||
|
|
||||||
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."
|
||||||
|
|
BIN
Tests/fonts/NotoColorEmoji.ttf
Normal file
|
@ -11,6 +11,7 @@ import tempfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image, ImageMath, features
|
from PIL import Image, ImageMath, features
|
||||||
|
|
||||||
|
@ -162,6 +163,16 @@ def skip_unless_feature(feature):
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
||||||
|
|
||||||
|
def skip_unless_feature_version(feature, version_required, reason=None):
|
||||||
|
if not features.check(feature):
|
||||||
|
return pytest.mark.skip(f"{feature} not available")
|
||||||
|
if reason is None:
|
||||||
|
reason = f"{feature} is older than {version_required}"
|
||||||
|
version_required = parse_version(version_required)
|
||||||
|
version_available = parse_version(features.version(feature))
|
||||||
|
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS")
|
@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS")
|
||||||
class PillowLeakTestCase:
|
class PillowLeakTestCase:
|
||||||
# requires unix/macOS
|
# requires unix/macOS
|
||||||
|
|
BIN
Tests/images/bitmap_font_1_basic.png
Normal file
After Width: | Height: | Size: 481 B |
BIN
Tests/images/bitmap_font_1_raqm.png
Normal file
After Width: | Height: | Size: 480 B |
BIN
Tests/images/bitmap_font_2_basic.png
Normal file
After Width: | Height: | Size: 661 B |
BIN
Tests/images/bitmap_font_2_raqm.png
Normal file
After Width: | Height: | Size: 658 B |
BIN
Tests/images/bitmap_font_4_basic.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Tests/images/bitmap_font_4_raqm.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Tests/images/bitmap_font_8_basic.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Tests/images/bitmap_font_8_raqm.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Tests/images/cbdt_notocoloremoji.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
Tests/images/cbdt_notocoloremoji_mask.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Tests/images/colr_bungee.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Tests/images/colr_bungee_mask.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 199 B |
BIN
Tests/images/imagedraw_arc_high.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 314 B |
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 428 B |
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 426 B |
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 430 B |
Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 404 B |
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 237 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 301 B |
BIN
Tests/images/imagedraw_chord_too_fat.png
Normal file
After Width: | Height: | Size: 519 B |
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 493 B |
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 514 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 408 B |
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 622 B |
BIN
Tests/images/imagedraw_ellipse_various_sizes.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
Tests/images/imagedraw_ellipse_various_sizes_filled.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 439 B |
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 465 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 333 B |
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 267 B |
Before Width: | Height: | Size: 394 B After Width: | Height: | Size: 375 B |
BIN
Tests/images/imagedraw_pieslice_wide.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 493 B |
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 519 B |
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 405 B |
BIN
Tests/images/standard_embedded.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
|
@ -60,6 +60,10 @@ class TestDecompressionBomb:
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
Image.open("Tests/images/decompression_bomb.gif")
|
Image.open("Tests/images/decompression_bomb.gif")
|
||||||
|
|
||||||
|
def test_exception_bmp(self):
|
||||||
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
|
Image.open("Tests/images/bmp/b/reallybig.bmp")
|
||||||
|
|
||||||
|
|
||||||
class TestDecompressionCrop:
|
class TestDecompressionCrop:
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -498,6 +498,12 @@ class TestImage:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.core.fill("RGB", (2, -2), (0, 0, 0))
|
Image.core.fill("RGB", (2, -2), (0, 0, 0))
|
||||||
|
|
||||||
|
def test_one_item_tuple(self):
|
||||||
|
for mode in ("I", "F", "L"):
|
||||||
|
im = Image.new(mode, (100, 100), (5,))
|
||||||
|
px = im.load()
|
||||||
|
assert px[0, 0] == 5
|
||||||
|
|
||||||
def test_linear_gradient_wrong_mode(self):
|
def test_linear_gradient_wrong_mode(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
wrong_mode = "RGB"
|
wrong_mode = "RGB"
|
||||||
|
|
|
@ -330,21 +330,22 @@ class TestCffi(AccessTest):
|
||||||
class TestImagePutPixelError(AccessTest):
|
class TestImagePutPixelError(AccessTest):
|
||||||
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"]
|
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"]
|
||||||
IMAGE_MODES2 = ["I", "I;16", "BGR;15"]
|
IMAGE_MODES2 = ["I", "I;16", "BGR;15"]
|
||||||
INVALID_TYPES1 = ["foo", 1.0, None]
|
INVALID_TYPES = ["foo", 1.0, None]
|
||||||
INVALID_TYPES2 = [*INVALID_TYPES1, (10,)]
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", IMAGE_MODES1)
|
@pytest.mark.parametrize("mode", IMAGE_MODES1)
|
||||||
def test_putpixel_type_error1(self, mode):
|
def test_putpixel_type_error1(self, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
for v in self.INVALID_TYPES1:
|
for v in self.INVALID_TYPES:
|
||||||
with pytest.raises(TypeError, match="color must be int or tuple"):
|
with pytest.raises(TypeError, match="color must be int or tuple"):
|
||||||
im.putpixel((0, 0), v)
|
im.putpixel((0, 0), v)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", IMAGE_MODES2)
|
@pytest.mark.parametrize("mode", IMAGE_MODES2)
|
||||||
def test_putpixel_type_error2(self, mode):
|
def test_putpixel_type_error2(self, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
for v in self.INVALID_TYPES2:
|
for v in self.INVALID_TYPES:
|
||||||
with pytest.raises(TypeError, match="color must be int"):
|
with pytest.raises(
|
||||||
|
TypeError, match="color must be int or single-element tuple"
|
||||||
|
):
|
||||||
im.putpixel((0, 0), v)
|
im.putpixel((0, 0), v)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2)
|
@pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2)
|
||||||
|
|
|
@ -311,8 +311,8 @@ def test_subtract():
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert new.getbbox() == (25, 50, 76, 76)
|
assert new.getbbox() == (25, 50, 76, 76)
|
||||||
assert new.getpixel((50, 50)) == GREEN
|
assert new.getpixel((50, 51)) == GREEN
|
||||||
assert new.getpixel((50, 51)) == BLACK
|
assert new.getpixel((50, 52)) == BLACK
|
||||||
|
|
||||||
|
|
||||||
def test_subtract_scale_offset():
|
def test_subtract_scale_offset():
|
||||||
|
@ -350,8 +350,8 @@ def test_subtract_modulo():
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert new.getbbox() == (25, 50, 76, 76)
|
assert new.getbbox() == (25, 50, 76, 76)
|
||||||
assert new.getpixel((50, 50)) == GREEN
|
assert new.getpixel((50, 51)) == GREEN
|
||||||
assert new.getpixel((50, 51)) == BLACK
|
assert new.getpixel((50, 52)) == BLACK
|
||||||
|
|
||||||
|
|
||||||
def test_subtract_modulo_no_clip():
|
def test_subtract_modulo_no_clip():
|
||||||
|
|
|
@ -164,6 +164,19 @@ def test_arc_width_non_whole_angle():
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_arc_high():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (200, 200))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.arc([10, 10, 89, 189], 20, 330, width=20, fill="white")
|
||||||
|
draw.arc([110, 10, 189, 189], 20, 150, width=20, fill="white")
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal(im, Image.open("Tests/images/imagedraw_arc_high.png"))
|
||||||
|
|
||||||
|
|
||||||
def test_bitmap():
|
def test_bitmap():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -194,13 +207,11 @@ def helper_chord(mode, bbox, start, end):
|
||||||
def test_chord1():
|
def test_chord1():
|
||||||
for mode in ["RGB", "L"]:
|
for mode in ["RGB", "L"]:
|
||||||
helper_chord(mode, BBOX1, 0, 180)
|
helper_chord(mode, BBOX1, 0, 180)
|
||||||
helper_chord(mode, BBOX1, 0.5, 180.4)
|
|
||||||
|
|
||||||
|
|
||||||
def test_chord2():
|
def test_chord2():
|
||||||
for mode in ["RGB", "L"]:
|
for mode in ["RGB", "L"]:
|
||||||
helper_chord(mode, BBOX2, 0, 180)
|
helper_chord(mode, BBOX2, 0, 180)
|
||||||
helper_chord(mode, BBOX2, 0.5, 180.4)
|
|
||||||
|
|
||||||
|
|
||||||
def test_chord_width():
|
def test_chord_width():
|
||||||
|
@ -240,6 +251,18 @@ def test_chord_zero_width():
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_chord_too_fat():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (100, 100))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.chord([-150, -150, 99, 99], 15, 60, width=10, fill="white", outline="red")
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal(im, Image.open("Tests/images/imagedraw_chord_too_fat.png"))
|
||||||
|
|
||||||
|
|
||||||
def helper_ellipse(mode, bbox):
|
def helper_ellipse(mode, bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
|
@ -282,15 +305,18 @@ def test_ellipse_edge():
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(((0, 0), (W - 1, H)), fill="white")
|
draw.ellipse(((0, 0), (W - 1, H - 1)), fill="white")
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_symmetric():
|
def test_ellipse_symmetric():
|
||||||
for bbox in [(25, 25, 76, 76), (25, 25, 75, 75)]:
|
for width, bbox in (
|
||||||
im = Image.new("RGB", (101, 101))
|
(100, (24, 24, 75, 75)),
|
||||||
|
(101, (25, 25, 75, 75)),
|
||||||
|
):
|
||||||
|
im = Image.new("RGB", (width, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.ellipse(bbox, fill="green", outline="blue")
|
draw.ellipse(bbox, fill="green", outline="blue")
|
||||||
assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT))
|
assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT))
|
||||||
|
@ -345,6 +371,43 @@ def test_ellipse_zero_width():
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def ellipse_various_sizes_helper(filled):
|
||||||
|
ellipse_sizes = range(32)
|
||||||
|
image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1
|
||||||
|
im = Image.new("RGB", (image_size, image_size))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
x = 1
|
||||||
|
for w in ellipse_sizes:
|
||||||
|
y = 1
|
||||||
|
for h in ellipse_sizes:
|
||||||
|
border = [x, y, x + w - 1, y + h - 1]
|
||||||
|
if filled:
|
||||||
|
draw.ellipse(border, fill="white")
|
||||||
|
else:
|
||||||
|
draw.ellipse(border, outline="white")
|
||||||
|
y += h + 1
|
||||||
|
x += w + 1
|
||||||
|
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
def test_ellipse_various_sizes():
|
||||||
|
im = ellipse_various_sizes_helper(False)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/imagedraw_ellipse_various_sizes.png") as expected:
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ellipse_various_sizes_filled():
|
||||||
|
im = ellipse_various_sizes_helper(True)
|
||||||
|
|
||||||
|
with Image.open(
|
||||||
|
"Tests/images/imagedraw_ellipse_various_sizes_filled.png"
|
||||||
|
) as expected:
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def helper_line(points):
|
def helper_line(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -420,13 +483,13 @@ def helper_pieslice(bbox, start, end):
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice1():
|
def test_pieslice1():
|
||||||
helper_pieslice(BBOX1, -90, 45)
|
helper_pieslice(BBOX1, -92, 46)
|
||||||
helper_pieslice(BBOX1, -90.5, 45.4)
|
helper_pieslice(BBOX1, -92.2, 46.2)
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice2():
|
def test_pieslice2():
|
||||||
helper_pieslice(BBOX2, -90, 45)
|
helper_pieslice(BBOX2, -92, 46)
|
||||||
helper_pieslice(BBOX2, -90.5, 45.4)
|
helper_pieslice(BBOX2, -92.2, 46.2)
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice_width():
|
def test_pieslice_width():
|
||||||
|
@ -467,6 +530,18 @@ def test_pieslice_zero_width():
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pieslice_wide():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (200, 100))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.pieslice([0, 0, 199, 99], 190, 170, width=10, fill="white", outline="red")
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice_wide.png"))
|
||||||
|
|
||||||
|
|
||||||
def helper_point(points):
|
def helper_point(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
|
|
@ -79,7 +79,7 @@ def test_ellipse_edge():
|
||||||
brush = ImageDraw2.Brush("white")
|
brush = ImageDraw2.Brush("white")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(((0, 0), (W - 1, H)), brush)
|
draw.ellipse(((0, 0), (W - 1, H - 1)), brush)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1)
|
assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1)
|
||||||
|
|
|
@ -18,6 +18,7 @@ from .helper import (
|
||||||
is_pypy,
|
is_pypy,
|
||||||
is_win32,
|
is_win32,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
skip_unless_feature_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
FONT_PATH = "Tests/fonts/FreeMono.ttf"
|
FONT_PATH = "Tests/fonts/FreeMono.ttf"
|
||||||
|
@ -901,18 +902,120 @@ class TestImageFont:
|
||||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
|
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skip_unless_feature("freetype2")
|
||||||
|
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
||||||
|
def test_bitmap_font(self, bpp):
|
||||||
|
text = "Bitmap Font"
|
||||||
|
layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE]
|
||||||
|
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
f"Tests/fonts/DejaVuSans-24-{bpp}-stripped.ttf",
|
||||||
|
24,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (160, 35), "white")
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.text((2, 2), text, "black", font)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, target)
|
||||||
|
|
||||||
|
def test_standard_embedded_color(self):
|
||||||
|
txt = "Hello World!"
|
||||||
|
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=self.LAYOUT_ENGINE)
|
||||||
|
ttf.getsize(txt)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (300, 64), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/standard_embedded.png") as expected:
|
||||||
|
assert_image_similar(im, expected, max(self.metrics["multiline"], 3))
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||||
|
@pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
|
||||||
|
def test_cbdt(self):
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
"Tests/fonts/NotoColorEmoji.ttf",
|
||||||
|
size=109,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (150, 150), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.text((10, 10), "\U0001f469", embedded_color=True, font=font)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/cbdt_notocoloremoji.png") as expected:
|
||||||
|
assert_image_similar(im, expected, self.metrics["multiline"])
|
||||||
|
except IOError as e:
|
||||||
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
|
pytest.skip("freetype compiled without libpng or unsupported")
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||||
|
@pytest.mark.xfail(is_pypy(), reason="failing on PyPy with Raqm")
|
||||||
|
def test_cbdt_mask(self):
|
||||||
|
try:
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
"Tests/fonts/NotoColorEmoji.ttf",
|
||||||
|
size=109,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (150, 150), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.text((10, 10), "\U0001f469", "black", font=font)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/cbdt_notocoloremoji_mask.png") as expected:
|
||||||
|
assert_image_similar(im, expected, self.metrics["multiline"])
|
||||||
|
except IOError as e:
|
||||||
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
|
pytest.skip("freetype compiled without libpng or unsupported")
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
|
def test_colr(self):
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
||||||
|
size=64,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (300, 75), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.text((15, 5), "Bungee", embedded_color=True, font=font)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/colr_bungee.png") as expected:
|
||||||
|
assert_image_similar(im, expected, 21)
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
|
def test_colr_mask(self):
|
||||||
|
font = ImageFont.truetype(
|
||||||
|
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
||||||
|
size=64,
|
||||||
|
layout_engine=self.LAYOUT_ENGINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (300, 75), "white")
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
d.text((15, 5), "Bungee", "black", font=font)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/colr_bungee_mask.png") as expected:
|
||||||
|
assert_image_similar(im, expected, 22)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("raqm")
|
@skip_unless_feature("raqm")
|
||||||
class TestImageFont_RaqmLayout(TestImageFont):
|
class TestImageFont_RaqmLayout(TestImageFont):
|
||||||
LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM
|
LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature_version("freetype2", "2.4", "Different metrics")
|
||||||
def test_render_mono_size():
|
def test_render_mono_size():
|
||||||
# issue 4177
|
# issue 4177
|
||||||
|
|
||||||
if parse_version(ImageFont.core.freetype2_version) < parse_version("2.4"):
|
|
||||||
pytest.skip("Different metrics")
|
|
||||||
|
|
||||||
im = Image.new("P", (100, 30), "white")
|
im = Image.new("P", (100, 30), "white")
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
ttf = ImageFont.truetype(
|
ttf = ImageFont.truetype(
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
|
|
||||||
from .helper import assert_image_similar
|
|
||||||
|
|
||||||
image_font_installed = True
|
|
||||||
try:
|
|
||||||
ImageFont.core.getfont
|
|
||||||
except ImportError:
|
|
||||||
image_font_installed = False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not image_font_installed, reason="Image font not installed")
|
|
||||||
def test_similar():
|
|
||||||
text = "EmbeddedBitmap"
|
|
||||||
font_outline = ImageFont.truetype(font="Tests/fonts/DejaVuSans.ttf", size=24)
|
|
||||||
font_bitmap = ImageFont.truetype(font="Tests/fonts/DejaVuSans-bitmap.ttf", size=24)
|
|
||||||
size_outline = font_outline.getsize(text)
|
|
||||||
size_bitmap = font_bitmap.getsize(text)
|
|
||||||
size_final = (
|
|
||||||
max(size_outline[0], size_bitmap[0]),
|
|
||||||
max(size_outline[1], size_bitmap[1]),
|
|
||||||
)
|
|
||||||
im_bitmap = Image.new("RGB", size_final, (255, 255, 255))
|
|
||||||
im_outline = im_bitmap.copy()
|
|
||||||
draw_bitmap = ImageDraw.Draw(im_bitmap)
|
|
||||||
draw_outline = ImageDraw.Draw(im_outline)
|
|
||||||
|
|
||||||
# Metrics are different on the bitmap and TTF fonts,
|
|
||||||
# more so on some platforms and versions of FreeType than others.
|
|
||||||
# Mac has a 1px difference, Linux doesn't.
|
|
||||||
draw_bitmap.text(
|
|
||||||
(0, size_final[1] - size_bitmap[1]), text, fill=(0, 0, 0), font=font_bitmap
|
|
||||||
)
|
|
||||||
draw_outline.text(
|
|
||||||
(0, size_final[1] - size_outline[1]),
|
|
||||||
text,
|
|
||||||
fill=(0, 0, 0),
|
|
||||||
font=font_outline,
|
|
||||||
)
|
|
||||||
assert_image_similar(im_bitmap, im_outline, 20)
|
|
|
@ -3,7 +3,11 @@ from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, features
|
from PIL import Image, ImageDraw, ImageFont, features
|
||||||
|
|
||||||
from .helper import assert_image_similar, skip_unless_feature
|
from .helper import (
|
||||||
|
assert_image_similar,
|
||||||
|
skip_unless_feature,
|
||||||
|
skip_unless_feature_version,
|
||||||
|
)
|
||||||
|
|
||||||
FONT_SIZE = 20
|
FONT_SIZE = 20
|
||||||
FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
|
FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
|
||||||
|
@ -262,13 +266,13 @@ def test_getlength_combine(mode, direction, text):
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
|
|
||||||
def test_anchor_ttb(anchor):
|
|
||||||
if parse_version(features.version_module("freetype2")) < parse_version("2.5.1"):
|
|
||||||
# FreeType 2.5.1 README: Miscellaneous Changes:
|
# FreeType 2.5.1 README: Miscellaneous Changes:
|
||||||
# Improved computation of emulated vertical metrics for TrueType fonts.
|
# Improved computation of emulated vertical metrics for TrueType fonts.
|
||||||
pytest.skip("FreeType <2.5.1 has incompatible ttb metrics")
|
@skip_unless_feature_version(
|
||||||
|
"freetype2", "2.5.1", "FreeType <2.5.1 has incompatible ttb metrics"
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
|
||||||
|
def test_anchor_ttb(anchor):
|
||||||
text = "f"
|
text = "f"
|
||||||
path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png"
|
path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png"
|
||||||
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120)
|
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120)
|
||||||
|
|
|
@ -438,11 +438,11 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Fedora 32 | 3.8 |x86-64 |
|
| Fedora 32 | 3.8 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, PyPy3 |x86-64 |
|
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Ubuntu Linux 16.04 LTS (Xenial) | 3.6, 3.7, 3.8, PyPy3 |x86-64 |
|
| Ubuntu Linux 16.04 LTS (Xenial) | 3.6, 3.7, 3.8, PyPy3 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Ubuntu Linux 18.04 LTS (Bionic) | 3.6, 3.7, 3.8, PyPy3 |x86-64 |
|
| Ubuntu Linux 18.04 LTS (Bionic) | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 |x86-64 |
|
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
|
@ -450,7 +450,7 @@ These platforms are built and tested for every change.
|
||||||
| +--------------------------+-----------------------+
|
| +--------------------------+-----------------------+
|
||||||
| | 3.6 |x86-64 |
|
| | 3.6 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Windows Server 2019 | 3.6, 3.7, 3.8 |x86, x86-64 |
|
| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9 |x86, x86-64 |
|
||||||
| +--------------------------+-----------------------+
|
| +--------------------------+-----------------------+
|
||||||
| | PyPy3 |x86 |
|
| | PyPy3 |x86 |
|
||||||
| +--------------------------+-----------------------+
|
| +--------------------------+-----------------------+
|
||||||
|
|
|
@ -291,7 +291,7 @@ Methods
|
||||||
|
|
||||||
Draw a shape.
|
Draw a shape.
|
||||||
|
|
||||||
.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None)
|
.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -352,7 +352,12 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.2.0
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None)
|
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -399,6 +404,19 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 6.0.0
|
.. versionadded:: 6.0.0
|
||||||
|
|
||||||
|
:param stroke_width: The width of the text stroke.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
|
:param stroke_fill: Color to use for the text stroke. If not given, will default to
|
||||||
|
the ``fill`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 6.2.0
|
||||||
|
|
||||||
|
:param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT).
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
.. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
|
.. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
|
||||||
|
|
||||||
Return the size of the given string, in pixels.
|
Return the size of the given string, in pixels.
|
||||||
|
|
|
@ -86,15 +86,11 @@ A new method :py:meth:`.ImageDraw.regular_polygon`, draws a regular polygon of `
|
||||||
For example ``draw.regular_polygon(((100, 100), 50), 5)``
|
For example ``draw.regular_polygon(((100, 100), 50), 5)``
|
||||||
draws a pentagon centered at the point ``(100, 100)`` with a polygon radius of ``50``.
|
draws a pentagon centered at the point ``(100, 100)`` with a polygon radius of ``50``.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
Error for large BMP files
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Previously, if a BMP file was too large, an ``OSError`` would be raised. Now,
|
||||||
|
``DecompressionBombError`` is used instead, as Pillow already uses for other formats.
|
||||||
|
|
|
@ -162,10 +162,6 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
else (1 << file_info["bits"])
|
else (1 << file_info["bits"])
|
||||||
)
|
)
|
||||||
|
|
||||||
# ------------------------------- Check abnormal values for DOS attacks
|
|
||||||
if file_info["width"] * file_info["height"] > 2 ** 31:
|
|
||||||
raise OSError("Unsupported BMP Size: (%dx%d)" % self.size)
|
|
||||||
|
|
||||||
# ---------------------- Check bit depth for unusual unsupported values
|
# ---------------------- Check bit depth for unusual unsupported values
|
||||||
self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
||||||
if self.mode is None:
|
if self.mode is None:
|
||||||
|
|
|
@ -3285,7 +3285,7 @@ class Exif(MutableMapping):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _fixup_dict(self, src_dict):
|
def _fixup_dict(self, src_dict):
|
||||||
# Helper function for _getexif()
|
# Helper function
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
return {k: self._fixup(v) for k, v in src_dict.items()}
|
return {k: self._fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ class ImageDraw:
|
||||||
fill = self.draw.draw_ink(fill)
|
fill = self.draw.draw_ink(fill)
|
||||||
return ink, fill
|
return ink, fill
|
||||||
|
|
||||||
def arc(self, xy, start, end, fill=None, width=0):
|
def arc(self, xy, start, end, fill=None, width=1):
|
||||||
"""Draw an arc."""
|
"""Draw an arc."""
|
||||||
ink, fill = self._getink(fill)
|
ink, fill = self._getink(fill)
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
|
@ -282,6 +282,7 @@ class ImageDraw:
|
||||||
language=None,
|
language=None,
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
stroke_fill=None,
|
stroke_fill=None,
|
||||||
|
embedded_color=False,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
@ -299,8 +300,12 @@ class ImageDraw:
|
||||||
language,
|
language,
|
||||||
stroke_width,
|
stroke_width,
|
||||||
stroke_fill,
|
stroke_fill,
|
||||||
|
embedded_color,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
|
raise ValueError("Embedded color supported only in RGB and RGBA modes")
|
||||||
|
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.getfont()
|
font = self.getfont()
|
||||||
|
|
||||||
|
@ -311,16 +316,20 @@ class ImageDraw:
|
||||||
return ink
|
return ink
|
||||||
|
|
||||||
def draw_text(ink, stroke_width=0, stroke_offset=None):
|
def draw_text(ink, stroke_width=0, stroke_offset=None):
|
||||||
|
mode = self.fontmode
|
||||||
|
if stroke_width == 0 and embedded_color:
|
||||||
|
mode = "RGBA"
|
||||||
coord = xy
|
coord = xy
|
||||||
try:
|
try:
|
||||||
mask, offset = font.getmask2(
|
mask, offset = font.getmask2(
|
||||||
text,
|
text,
|
||||||
self.fontmode,
|
mode,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
features=features,
|
features=features,
|
||||||
language=language,
|
language=language,
|
||||||
stroke_width=stroke_width,
|
stroke_width=stroke_width,
|
||||||
anchor=anchor,
|
anchor=anchor,
|
||||||
|
ink=ink,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -329,12 +338,13 @@ class ImageDraw:
|
||||||
try:
|
try:
|
||||||
mask = font.getmask(
|
mask = font.getmask(
|
||||||
text,
|
text,
|
||||||
self.fontmode,
|
mode,
|
||||||
direction,
|
direction,
|
||||||
features,
|
features,
|
||||||
language,
|
language,
|
||||||
stroke_width,
|
stroke_width,
|
||||||
anchor,
|
anchor,
|
||||||
|
ink,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -342,6 +352,14 @@ class ImageDraw:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
if stroke_offset:
|
if stroke_offset:
|
||||||
coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
|
coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]
|
||||||
|
if mode == "RGBA":
|
||||||
|
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
|
||||||
|
# extract mask and set text alpha
|
||||||
|
color, mask = mask, mask.getband(3)
|
||||||
|
color.fillband(3, (ink >> 24) & 0xFF)
|
||||||
|
coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1]
|
||||||
|
self.im.paste(color, coord + coord2, mask)
|
||||||
|
else:
|
||||||
self.draw.draw_bitmap(coord, mask, ink)
|
self.draw.draw_bitmap(coord, mask, ink)
|
||||||
|
|
||||||
ink = getink(fill)
|
ink = getink(fill)
|
||||||
|
@ -374,6 +392,7 @@ class ImageDraw:
|
||||||
language=None,
|
language=None,
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
stroke_fill=None,
|
stroke_fill=None,
|
||||||
|
embedded_color=False,
|
||||||
):
|
):
|
||||||
if direction == "ttb":
|
if direction == "ttb":
|
||||||
raise ValueError("ttb direction is unsupported for multiline text")
|
raise ValueError("ttb direction is unsupported for multiline text")
|
||||||
|
@ -435,6 +454,7 @@ class ImageDraw:
|
||||||
language=language,
|
language=language,
|
||||||
stroke_width=stroke_width,
|
stroke_width=stroke_width,
|
||||||
stroke_fill=stroke_fill,
|
stroke_fill=stroke_fill,
|
||||||
|
embedded_color=embedded_color,
|
||||||
)
|
)
|
||||||
top += line_spacing
|
top += line_spacing
|
||||||
|
|
||||||
|
|
|
@ -413,7 +413,7 @@ class FreeTypeFont:
|
||||||
"""
|
"""
|
||||||
# vertical offset is added for historical reasons
|
# vertical offset is added for historical reasons
|
||||||
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
|
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
|
||||||
size, offset = self.font.getsize(text, False, direction, features, language)
|
size, offset = self.font.getsize(text, "L", direction, features, language)
|
||||||
return (
|
return (
|
||||||
size[0] + stroke_width * 2,
|
size[0] + stroke_width * 2,
|
||||||
size[1] + stroke_width * 2 + offset[1],
|
size[1] + stroke_width * 2 + offset[1],
|
||||||
|
@ -500,12 +500,14 @@ class FreeTypeFont:
|
||||||
language=None,
|
language=None,
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
anchor=None,
|
anchor=None,
|
||||||
|
ink=0,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
||||||
maximum value of 255. Otherwise, it should have mode ``1``.
|
maximum value of 255. If the font has embedded color data, the bitmap
|
||||||
|
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
|
||||||
|
|
||||||
:param text: Text to render.
|
:param text: Text to render.
|
||||||
:param mode: Used by some graphics drivers to indicate what mode the
|
:param mode: Used by some graphics drivers to indicate what mode the
|
||||||
|
@ -554,6 +556,10 @@ class FreeTypeFont:
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param ink: Foreground ink for rendering in RGBA mode.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
:return: An internal PIL storage memory instance as defined by the
|
:return: An internal PIL storage memory instance as defined by the
|
||||||
:py:mod:`PIL.Image.core` interface module.
|
:py:mod:`PIL.Image.core` interface module.
|
||||||
"""
|
"""
|
||||||
|
@ -565,6 +571,7 @@ class FreeTypeFont:
|
||||||
language=language,
|
language=language,
|
||||||
stroke_width=stroke_width,
|
stroke_width=stroke_width,
|
||||||
anchor=anchor,
|
anchor=anchor,
|
||||||
|
ink=ink,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
def getmask2(
|
def getmask2(
|
||||||
|
@ -577,6 +584,7 @@ class FreeTypeFont:
|
||||||
language=None,
|
language=None,
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
anchor=None,
|
anchor=None,
|
||||||
|
ink=0,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
@ -584,7 +592,8 @@ class FreeTypeFont:
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
|
||||||
maximum value of 255. Otherwise, it should have mode ``1``.
|
maximum value of 255. If the font has embedded color data, the bitmap
|
||||||
|
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
|
||||||
|
|
||||||
:param text: Text to render.
|
:param text: Text to render.
|
||||||
:param mode: Used by some graphics drivers to indicate what mode the
|
:param mode: Used by some graphics drivers to indicate what mode the
|
||||||
|
@ -633,18 +642,22 @@ class FreeTypeFont:
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param ink: Foreground ink for rendering in RGBA mode.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
:return: A tuple of an internal PIL storage memory instance as defined by the
|
:return: A tuple of an internal PIL storage memory instance as defined by the
|
||||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||||
gap between the starting coordinate and the first marking
|
gap between the starting coordinate and the first marking
|
||||||
"""
|
"""
|
||||||
size, offset = self.font.getsize(
|
size, offset = self.font.getsize(
|
||||||
text, mode == "1", direction, features, language, anchor
|
text, mode, direction, features, language, anchor
|
||||||
)
|
)
|
||||||
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
||||||
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
||||||
im = fill("L", size, 0)
|
im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
|
||||||
self.font.render(
|
self.font.render(
|
||||||
text, im.id, mode == "1", direction, features, language, stroke_width
|
text, im.id, mode, direction, features, language, stroke_width, ink
|
||||||
)
|
)
|
||||||
return im, offset
|
return im, offset
|
||||||
|
|
||||||
|
|
|
@ -475,13 +475,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return _getmp(self)
|
return _getmp(self)
|
||||||
|
|
||||||
|
|
||||||
def _fixup_dict(src_dict):
|
|
||||||
# Helper function for _getexif()
|
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
|
||||||
exif = Image.Exif()
|
|
||||||
return exif._fixup_dict(src_dict)
|
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -518,6 +518,9 @@ getink(PyObject* color, Imaging im, char* ink)
|
||||||
be cast to either UINT8 or INT32 */
|
be cast to either UINT8 or INT32 */
|
||||||
|
|
||||||
int rIsInt = 0;
|
int rIsInt = 0;
|
||||||
|
if (PyTuple_Check(color) && PyTuple_Size(color) == 1) {
|
||||||
|
color = PyTuple_GetItem(color, 0);
|
||||||
|
}
|
||||||
if (im->type == IMAGING_TYPE_UINT8 ||
|
if (im->type == IMAGING_TYPE_UINT8 ||
|
||||||
im->type == IMAGING_TYPE_INT32 ||
|
im->type == IMAGING_TYPE_INT32 ||
|
||||||
im->type == IMAGING_TYPE_SPECIAL) {
|
im->type == IMAGING_TYPE_SPECIAL) {
|
||||||
|
@ -533,7 +536,7 @@ getink(PyObject* color, Imaging im, char* ink)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PyErr_SetString(PyExc_TypeError, "color must be int");
|
PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2836,8 +2839,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args)
|
||||||
int ink;
|
int ink;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
float start, end;
|
float start, end;
|
||||||
int op = 0;
|
if (!PyArg_ParseTuple(args, "Offi|i", &data, &start, &end, &ink, &width)) {
|
||||||
if (!PyArg_ParseTuple(args, "Offi|ii", &data, &start, &end, &ink, &width)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2854,7 +2856,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args)
|
||||||
n = ImagingDrawArc(self->image->image,
|
n = ImagingDrawArc(self->image->image,
|
||||||
(int) xy[0], (int) xy[1],
|
(int) xy[0], (int) xy[1],
|
||||||
(int) xy[2], (int) xy[3],
|
(int) xy[2], (int) xy[3],
|
||||||
start, end, &ink, width, op
|
start, end, &ink, width, self->blend
|
||||||
);
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
198
src/_imagingft.c
|
@ -25,9 +25,13 @@
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include FT_GLYPH_H
|
#include FT_GLYPH_H
|
||||||
|
#include FT_BITMAP_H
|
||||||
#include FT_STROKER_H
|
#include FT_STROKER_H
|
||||||
#include FT_MULTIPLE_MASTERS_H
|
#include FT_MULTIPLE_MASTERS_H
|
||||||
#include FT_SFNT_NAMES_H
|
#include FT_SFNT_NAMES_H
|
||||||
|
#ifdef FT_COLOR_H
|
||||||
|
#include FT_COLOR_H
|
||||||
|
#endif
|
||||||
|
|
||||||
#define KEEP_PY_UNICODE
|
#define KEEP_PY_UNICODE
|
||||||
|
|
||||||
|
@ -350,7 +354,7 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out)
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
||||||
const char* lang, GlyphInfo **glyph_info, int mask)
|
const char* lang, GlyphInfo **glyph_info, int mask, int color)
|
||||||
{
|
{
|
||||||
size_t i = 0, count = 0, start = 0;
|
size_t i = 0, count = 0, start = 0;
|
||||||
raqm_t *rq;
|
raqm_t *rq;
|
||||||
|
@ -529,7 +533,7 @@ failed:
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
||||||
const char* lang, GlyphInfo **glyph_info, int mask)
|
const char* lang, GlyphInfo **glyph_info, int mask, int color)
|
||||||
{
|
{
|
||||||
int error, load_flags;
|
int error, load_flags;
|
||||||
FT_ULong ch;
|
FT_ULong ch;
|
||||||
|
@ -561,10 +565,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
load_flags = FT_LOAD_NO_BITMAP;
|
load_flags = FT_LOAD_DEFAULT;
|
||||||
if (mask) {
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
}
|
}
|
||||||
|
#ifdef FT_LOAD_COLOR
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
for (i = 0; font_getchar(string, i, &ch); i++) {
|
for (i = 0; font_getchar(string, i, &ch); i++) {
|
||||||
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
|
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
|
||||||
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
|
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
|
||||||
|
@ -595,14 +604,14 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features,
|
||||||
const char* lang, GlyphInfo **glyph_info, int mask)
|
const char* lang, GlyphInfo **glyph_info, int mask, int color)
|
||||||
{
|
{
|
||||||
size_t count;
|
size_t count;
|
||||||
|
|
||||||
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
|
if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||||
count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask);
|
count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask, color);
|
||||||
} else {
|
} else {
|
||||||
count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask);
|
count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask, color);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
@ -667,6 +676,8 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
size_t i, count; /* glyph_info index and length */
|
size_t i, count; /* glyph_info index and length */
|
||||||
int horizontal_dir; /* is primary axis horizontal? */
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
|
const char *mode = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
const char *anchor = NULL;
|
const char *anchor = NULL;
|
||||||
|
@ -675,12 +686,15 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
|
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O|izOzz:getsize", &string, &mask, &dir, &features, &lang, &anchor)) {
|
if (!PyArg_ParseTuple(args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
if (anchor == NULL) {
|
if (anchor == NULL) {
|
||||||
anchor = horizontal_dir ? "la" : "lt";
|
anchor = horizontal_dir ? "la" : "lt";
|
||||||
}
|
}
|
||||||
|
@ -688,18 +702,20 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
goto bad_anchor;
|
goto bad_anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960
|
load_flags = FT_LOAD_DEFAULT;
|
||||||
* Yifu Yu<root@jackyyf.com>, 2014-10-15
|
|
||||||
*/
|
|
||||||
load_flags = FT_LOAD_NO_BITMAP;
|
|
||||||
if (mask) {
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
}
|
}
|
||||||
|
#ifdef FT_LOAD_COLOR
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* text bounds are given by:
|
* text bounds are given by:
|
||||||
|
@ -865,19 +881,26 @@ font_render(FontObject* self, PyObject* args)
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
FT_GlyphSlot glyph_slot;
|
FT_GlyphSlot glyph_slot;
|
||||||
FT_Bitmap bitmap;
|
FT_Bitmap bitmap;
|
||||||
|
FT_Bitmap bitmap_converted; /* initialized lazily, for non-8bpp fonts */
|
||||||
FT_BitmapGlyph bitmap_glyph;
|
FT_BitmapGlyph bitmap_glyph;
|
||||||
FT_Stroker stroker = NULL;
|
FT_Stroker stroker = NULL;
|
||||||
|
int bitmap_converted_ready = 0; /* has bitmap_converted been initialized */
|
||||||
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
size_t i, count; /* glyph_info index and length */
|
size_t i, count; /* glyph_info index and length */
|
||||||
int xx, yy; /* pixel offset of current glyph bitmap */
|
int xx, yy; /* pixel offset of current glyph bitmap */
|
||||||
int x0, x1; /* horizontal bounds of glyph bitmap to copy */
|
int x0, x1; /* horizontal bounds of glyph bitmap to copy */
|
||||||
unsigned int bitmap_y; /* glyph bitmap y index */
|
unsigned int bitmap_y; /* glyph bitmap y index */
|
||||||
unsigned char *source; /* glyph bitmap source buffer */
|
unsigned char *source; /* glyph bitmap source buffer */
|
||||||
|
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
|
||||||
Imaging im;
|
Imaging im;
|
||||||
Py_ssize_t id;
|
Py_ssize_t id;
|
||||||
int horizontal_dir; /* is primary axis horizontal? */
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
int stroke_width = 0;
|
int stroke_width = 0;
|
||||||
|
PY_LONG_LONG foreground_ink_long = 0;
|
||||||
|
unsigned int foreground_ink;
|
||||||
|
const char *mode = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
PyObject *features = Py_None;
|
PyObject *features = Py_None;
|
||||||
|
@ -886,14 +909,31 @@ font_render(FontObject* self, PyObject* args)
|
||||||
/* render string into given buffer (the buffer *must* have
|
/* render string into given buffer (the buffer *must* have
|
||||||
the right size, or this will crash) */
|
the right size, or this will crash) */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang,
|
if (!PyArg_ParseTuple(args, "On|zzOziL:render", &string, &id, &mode, &dir, &features, &lang,
|
||||||
&stroke_width)) {
|
&stroke_width, &foreground_ink_long)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
|
foreground_ink = foreground_ink_long;
|
||||||
|
|
||||||
|
#ifdef FT_COLOR_H
|
||||||
|
if (color) {
|
||||||
|
FT_Color foreground_color;
|
||||||
|
FT_Byte* ink = (FT_Byte*)&foreground_ink;
|
||||||
|
foreground_color.red = ink[0];
|
||||||
|
foreground_color.green = ink[1];
|
||||||
|
foreground_color.blue = ink[2];
|
||||||
|
foreground_color.alpha = (FT_Byte) 255; /* ink alpha is handled in ImageDraw.text */
|
||||||
|
FT_Palette_Set_Foreground_Color(self->face, foreground_color);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -911,12 +951,15 @@ font_render(FontObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
|
|
||||||
im = (Imaging) id;
|
im = (Imaging) id;
|
||||||
|
load_flags = FT_LOAD_DEFAULT;
|
||||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
|
||||||
load_flags = FT_LOAD_NO_BITMAP;
|
|
||||||
if (mask) {
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
}
|
}
|
||||||
|
#ifdef FT_LOAD_COLOR
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* calculate x_min and y_max
|
* calculate x_min and y_max
|
||||||
|
@ -988,6 +1031,55 @@ font_render(FontObject* self, PyObject* args)
|
||||||
yy = -(py + glyph_slot->bitmap_top);
|
yy = -(py + glyph_slot->bitmap_top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* convert non-8bpp bitmaps */
|
||||||
|
switch (bitmap.pixel_mode) {
|
||||||
|
case FT_PIXEL_MODE_MONO:
|
||||||
|
convert_scale = 255;
|
||||||
|
break;
|
||||||
|
case FT_PIXEL_MODE_GRAY2:
|
||||||
|
convert_scale = 255 / 3;
|
||||||
|
break;
|
||||||
|
case FT_PIXEL_MODE_GRAY4:
|
||||||
|
convert_scale = 255 / 15;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
convert_scale = 1;
|
||||||
|
}
|
||||||
|
switch (bitmap.pixel_mode) {
|
||||||
|
case FT_PIXEL_MODE_MONO:
|
||||||
|
case FT_PIXEL_MODE_GRAY2:
|
||||||
|
case FT_PIXEL_MODE_GRAY4:
|
||||||
|
if (!bitmap_converted_ready) {
|
||||||
|
|
||||||
|
#if FREETYPE_MAJOR > 2 ||\
|
||||||
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 6)
|
||||||
|
FT_Bitmap_Init(&bitmap_converted);
|
||||||
|
#else
|
||||||
|
FT_Bitmap_New(&bitmap_converted);
|
||||||
|
#endif
|
||||||
|
bitmap_converted_ready = 1;
|
||||||
|
}
|
||||||
|
error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1);
|
||||||
|
if (error) {
|
||||||
|
geterror(error);
|
||||||
|
goto glyph_error;
|
||||||
|
}
|
||||||
|
bitmap = bitmap_converted;
|
||||||
|
/* bitmap is now FT_PIXEL_MODE_GRAY, fall through */
|
||||||
|
case FT_PIXEL_MODE_GRAY:
|
||||||
|
break;
|
||||||
|
#ifdef FT_LOAD_COLOR
|
||||||
|
case FT_PIXEL_MODE_BGRA:
|
||||||
|
if (color) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* we didn't ask for color, fall through to default */
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
PyErr_SetString(PyExc_IOError, "unsupported bitmap pixel mode");
|
||||||
|
goto glyph_error;
|
||||||
|
}
|
||||||
|
|
||||||
/* clip glyph bitmap width to target image bounds */
|
/* clip glyph bitmap width to target image bounds */
|
||||||
x0 = 0;
|
x0 = 0;
|
||||||
x1 = bitmap.width;
|
x1 = bitmap.width;
|
||||||
|
@ -1002,29 +1094,55 @@ font_render(FontObject* self, PyObject* args)
|
||||||
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
|
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
|
||||||
/* clip glyph bitmap height to target image bounds */
|
/* clip glyph bitmap height to target image bounds */
|
||||||
if (yy >= 0 && yy < im->ysize) {
|
if (yy >= 0 && yy < im->ysize) {
|
||||||
// blend this glyph into the buffer
|
/* blend this glyph into the buffer */
|
||||||
unsigned char *target = im->image8[yy] + xx;
|
int k;
|
||||||
if (mask) {
|
unsigned char v;
|
||||||
// use monochrome mask (on palette images, etc)
|
unsigned char* target;
|
||||||
int j, k, m = 128;
|
if (color) {
|
||||||
for (j = k = 0; j < x1; j++) {
|
/* target[RGB] returns the color, target[A] returns the mask */
|
||||||
if (j >= x0 && (source[k] & m)) {
|
/* target bands get split again in ImageDraw.text */
|
||||||
target[j] = 255;
|
target = im->image[yy] + xx * 4;
|
||||||
|
} else {
|
||||||
|
target = im->image8[yy] + xx;
|
||||||
}
|
}
|
||||||
if (!(m >>= 1)) {
|
#ifdef FT_LOAD_COLOR
|
||||||
m = 128;
|
if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
|
||||||
k++;
|
/* paste color glyph */
|
||||||
|
for (k = x0; k < x1; k++) {
|
||||||
|
if (target[k * 4 + 3] < source[k * 4 + 3]) {
|
||||||
|
/* unpremultiply BGRa to RGBA */
|
||||||
|
target[k * 4 + 0] = CLIP8((255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]);
|
||||||
|
target[k * 4 + 1] = CLIP8((255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]);
|
||||||
|
target[k * 4 + 2] = CLIP8((255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]);
|
||||||
|
target[k * 4 + 3] = source[k * 4 + 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
|
||||||
|
if (color) {
|
||||||
|
unsigned char* ink = (unsigned char*)&foreground_ink;
|
||||||
|
for (k = x0; k < x1; k++) {
|
||||||
|
v = source[k] * convert_scale;
|
||||||
|
if (target[k * 4 + 3] < v) {
|
||||||
|
target[k * 4 + 0] = ink[0];
|
||||||
|
target[k * 4 + 1] = ink[1];
|
||||||
|
target[k * 4 + 2] = ink[2];
|
||||||
|
target[k * 4 + 3] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// use antialiased rendering
|
|
||||||
int k;
|
|
||||||
for (k = x0; k < x1; k++) {
|
for (k = x0; k < x1; k++) {
|
||||||
if (target[k] < source[k]) {
|
v = source[k] * convert_scale;
|
||||||
target[k] = source[k];
|
if (target[k] < v) {
|
||||||
|
target[k] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
PyErr_SetString(PyExc_IOError, "unsupported bitmap pixel mode");
|
||||||
|
goto glyph_error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
source += bitmap.pitch;
|
source += bitmap.pitch;
|
||||||
}
|
}
|
||||||
|
@ -1035,9 +1153,23 @@ font_render(FontObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bitmap_converted_ready) {
|
||||||
|
FT_Bitmap_Done(library, &bitmap_converted);
|
||||||
|
}
|
||||||
FT_Stroker_Done(stroker);
|
FT_Stroker_Done(stroker);
|
||||||
PyMem_Del(glyph_info);
|
PyMem_Del(glyph_info);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|
||||||
|
glyph_error:
|
||||||
|
if (stroker != NULL) {
|
||||||
|
FT_Done_Glyph(glyph);
|
||||||
|
}
|
||||||
|
if (bitmap_converted_ready) {
|
||||||
|
FT_Bitmap_Done(library, &bitmap_converted);
|
||||||
|
}
|
||||||
|
FT_Stroker_Done(stroker);
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FREETYPE_MAJOR > 2 ||\
|
#if FREETYPE_MAJOR > 2 ||\
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "Imaging.h"
|
#include "Imaging.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#define CEIL(v) (int) ceil(v)
|
#define CEIL(v) (int) ceil(v)
|
||||||
#define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v))
|
#define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v))
|
||||||
|
@ -818,221 +819,750 @@ ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void* ink,
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* standard shapes */
|
/* standard shapes */
|
||||||
|
|
||||||
#define ARC 0
|
// Imagine 2D plane and ellipse with center in (0, 0) and semi-major axes a and b.
|
||||||
#define CHORD 1
|
// Then quarter_* stuff approximates its top right quarter (x, y >= 0) with integer
|
||||||
#define PIESLICE 2
|
// points from set {(2x+x0, 2y+y0) | x,y in Z} where x0, y0 are from {0, 1} and
|
||||||
|
// are such that point (a, b) is in the set.
|
||||||
|
|
||||||
static void
|
typedef struct {
|
||||||
ellipsePoint(int cx, int cy, int w, int h,
|
int32_t a, b, cx, cy, ex, ey;
|
||||||
float i, int *x, int *y)
|
int64_t a2, b2, a2b2;
|
||||||
{
|
int8_t finished;
|
||||||
float i_cos, i_sin;
|
} quarter_state;
|
||||||
float x_f, y_f;
|
|
||||||
double modf_int;
|
void quarter_init(quarter_state* s, int32_t a, int32_t b) {
|
||||||
i_cos = cos(i*M_PI/180);
|
if (a < 0 || b < 0) {
|
||||||
i_sin = sin(i*M_PI/180);
|
s->finished = 1;
|
||||||
x_f = (i_cos * w/2) + cx;
|
|
||||||
y_f = (i_sin * h/2) + cy;
|
|
||||||
if (modf(x_f, &modf_int) == 0.5) {
|
|
||||||
*x = i_cos > 0 ? FLOOR(x_f) : CEIL(x_f);
|
|
||||||
} else {
|
} else {
|
||||||
*x = FLOOR(x_f + 0.5);
|
s->a = a;
|
||||||
|
s->b = b;
|
||||||
|
s->cx = a;
|
||||||
|
s->cy = b % 2;
|
||||||
|
s->ex = a % 2;
|
||||||
|
s->ey = b;
|
||||||
|
s->a2 = a * a;
|
||||||
|
s->b2 = b * b;
|
||||||
|
s->a2b2 = s->a2 * s->b2;
|
||||||
|
s->finished = 0;
|
||||||
}
|
}
|
||||||
if (modf(y_f, &modf_int) == 0.5) {
|
}
|
||||||
*y = i_sin > 0 ? FLOOR(y_f) : CEIL(y_f);
|
|
||||||
|
// deviation of the point from ellipse curve, basically a substitution
|
||||||
|
// of the point into the ellipse equation
|
||||||
|
int64_t quarter_delta(quarter_state* s, int64_t x, int64_t y) {
|
||||||
|
return llabs(s->a2 * y * y + s->b2 * x * x - s->a2b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t quarter_next(quarter_state* s, int32_t* ret_x, int32_t* ret_y) {
|
||||||
|
if (s->finished) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*ret_x = s->cx;
|
||||||
|
*ret_y = s->cy;
|
||||||
|
if (s->cx == s->ex && s->cy == s->ey) {
|
||||||
|
s->finished = 1;
|
||||||
} else {
|
} else {
|
||||||
*y = FLOOR(y_f + 0.5);
|
// Bresenham's algorithm, possible optimization: only consider 2 of 3
|
||||||
|
// next points depending on current slope
|
||||||
|
int32_t nx = s->cx;
|
||||||
|
int32_t ny = s->cy + 2;
|
||||||
|
int64_t ndelta = quarter_delta(s, nx, ny);
|
||||||
|
if (nx > 1) {
|
||||||
|
int64_t newdelta = quarter_delta(s, s->cx - 2, s->cy + 2);
|
||||||
|
if (ndelta > newdelta) {
|
||||||
|
nx = s->cx - 2;
|
||||||
|
ny = s->cy + 2;
|
||||||
|
ndelta = newdelta;
|
||||||
|
}
|
||||||
|
newdelta = quarter_delta(s, s->cx - 2, s->cy);
|
||||||
|
if (ndelta > newdelta) {
|
||||||
|
nx = s->cx - 2;
|
||||||
|
ny = s->cy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s->cx = nx;
|
||||||
static int
|
s->cy = ny;
|
||||||
ellipse(Imaging im, int x0, int y0, int x1, int y1,
|
|
||||||
float start, float end, const void* ink_, int fill,
|
|
||||||
int width, int mode, int op)
|
|
||||||
{
|
|
||||||
float i;
|
|
||||||
int inner;
|
|
||||||
int n;
|
|
||||||
int maxEdgeCount;
|
|
||||||
int w, h;
|
|
||||||
int x, y;
|
|
||||||
int cx, cy;
|
|
||||||
int lx = 0, ly = 0;
|
|
||||||
int sx = 0, sy = 0;
|
|
||||||
int lx_inner = 0, ly_inner = 0;
|
|
||||||
int sx_inner = 0, sy_inner = 0;
|
|
||||||
DRAW* draw;
|
|
||||||
INT32 ink;
|
|
||||||
Edge* e;
|
|
||||||
|
|
||||||
DRAWINIT();
|
|
||||||
|
|
||||||
while (end < start) {
|
|
||||||
end += 360;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end - start > 360) {
|
|
||||||
// no need to go in loops
|
|
||||||
end = start + 361;
|
|
||||||
}
|
|
||||||
|
|
||||||
w = x1 - x0;
|
|
||||||
h = y1 - y0;
|
|
||||||
if (w <= 0 || h <= 0) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cx = (x0 + x1) / 2;
|
// quarter_* stuff can "draw" a quarter of an ellipse with thickness 1, great.
|
||||||
cy = (y0 + y1) / 2;
|
// Now we use ellipse_* stuff to join all four quarters of two different sized
|
||||||
|
// ellipses and receive horizontal segments of a complete ellipse with
|
||||||
|
// specified thickness.
|
||||||
|
//
|
||||||
|
// Still using integer grid with step 2 at this point (like in quarter_*)
|
||||||
|
// to ease angle clipping in future.
|
||||||
|
|
||||||
if (!fill && width <= 1) {
|
typedef struct {
|
||||||
for (i = start; i < end+1; i++) {
|
quarter_state st_o, st_i;
|
||||||
if (i > end) {
|
int32_t py, pl, pr;
|
||||||
i = end;
|
int32_t cy[4], cl[4], cr[4];
|
||||||
}
|
int8_t bufcnt;
|
||||||
ellipsePoint(cx, cy, w, h, i, &x, &y);
|
int8_t finished;
|
||||||
if (i != start) {
|
int8_t leftmost;
|
||||||
draw->line(im, lx, ly, x, y, ink);
|
} ellipse_state;
|
||||||
|
|
||||||
|
void ellipse_init(ellipse_state* s, int32_t a, int32_t b, int32_t w) {
|
||||||
|
s->bufcnt = 0;
|
||||||
|
s->leftmost = a % 2;
|
||||||
|
quarter_init(&s->st_o, a, b);
|
||||||
|
if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) {
|
||||||
|
s->finished = 1;
|
||||||
} else {
|
} else {
|
||||||
sx = x, sy = y;
|
s->finished = 0;
|
||||||
|
quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1));
|
||||||
|
s->pl = s->leftmost;
|
||||||
}
|
}
|
||||||
lx = x, ly = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i != start) {
|
int8_t ellipse_next(ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) {
|
||||||
if (mode == PIESLICE) {
|
if (s->bufcnt == 0) {
|
||||||
if (x != cx || y != cy) {
|
if (s->finished) {
|
||||||
draw->line(im, x, y, cx, cy, ink);
|
return -1;
|
||||||
draw->line(im, cx, cy, sx, sy, ink);
|
|
||||||
}
|
|
||||||
} else if (mode == CHORD) {
|
|
||||||
if (x != sx || y != sy) {
|
|
||||||
draw->line(im, x, y, sx, sy, ink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
int32_t y = s->py;
|
||||||
|
int32_t l = s->pl;
|
||||||
|
int32_t r = s->pr;
|
||||||
|
int32_t cx = 0, cy = 0;
|
||||||
|
int8_t next_ret;
|
||||||
|
while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= y) {
|
||||||
}
|
}
|
||||||
|
if (next_ret == -1) {
|
||||||
|
s->finished = 1;
|
||||||
} else {
|
} else {
|
||||||
inner = (mode == ARC || !fill) ? 1 : 0;
|
s->pr = cx;
|
||||||
|
s->py = cy;
|
||||||
// Build edge list
|
|
||||||
// malloc check UNDONE, FLOAT?
|
|
||||||
maxEdgeCount = ceil(end - start);
|
|
||||||
if (inner) {
|
|
||||||
maxEdgeCount *= 2;
|
|
||||||
}
|
}
|
||||||
maxEdgeCount += 3;
|
while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= y) {
|
||||||
e = calloc(maxEdgeCount, sizeof(Edge));
|
l = cx;
|
||||||
if (!e) {
|
}
|
||||||
|
s->pl = next_ret == -1 ? s->leftmost : cx;
|
||||||
|
|
||||||
|
if ((l > 0 || l < r) && y > 0) {
|
||||||
|
s->cl[s->bufcnt] = l == 0 ? 2 : l;
|
||||||
|
s->cy[s->bufcnt] = y;
|
||||||
|
s->cr[s->bufcnt] = r;
|
||||||
|
++s->bufcnt;
|
||||||
|
}
|
||||||
|
if (y > 0) {
|
||||||
|
s->cl[s->bufcnt] = -r;
|
||||||
|
s->cy[s->bufcnt] = y;
|
||||||
|
s->cr[s->bufcnt] = -l;
|
||||||
|
++s->bufcnt;
|
||||||
|
}
|
||||||
|
if (l > 0 || l < r) {
|
||||||
|
s->cl[s->bufcnt] = l == 0 ? 2 : l;
|
||||||
|
s->cy[s->bufcnt] = -y;
|
||||||
|
s->cr[s->bufcnt] = r;
|
||||||
|
++s->bufcnt;
|
||||||
|
}
|
||||||
|
s->cl[s->bufcnt] = -r;
|
||||||
|
s->cy[s->bufcnt] = -y;
|
||||||
|
s->cr[s->bufcnt] = -l;
|
||||||
|
++s->bufcnt;
|
||||||
|
}
|
||||||
|
--s->bufcnt;
|
||||||
|
*ret_x0 = s->cl[s->bufcnt];
|
||||||
|
*ret_y = s->cy[s->bufcnt];
|
||||||
|
*ret_x1 = s->cr[s->bufcnt];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipping tree consists of half-plane clipping nodes and combining nodes.
|
||||||
|
// We can throw a horizontal segment in such a tree and collect an ordered set
|
||||||
|
// of resulting disjoint clipped segments organized into a sorted linked list
|
||||||
|
// of their end points.
|
||||||
|
typedef enum {
|
||||||
|
CT_AND, // intersection
|
||||||
|
CT_OR, // union
|
||||||
|
CT_CLIP // half-plane clipping
|
||||||
|
} clip_type;
|
||||||
|
|
||||||
|
typedef struct clip_node {
|
||||||
|
clip_type type;
|
||||||
|
double a, b, c; // half-plane coeffs, only used in clipping nodes
|
||||||
|
struct clip_node* l; // child pointers, are only non-NULL in combining nodes
|
||||||
|
struct clip_node* r;
|
||||||
|
} clip_node;
|
||||||
|
|
||||||
|
// Linked list for the ends of the clipped horizontal segments.
|
||||||
|
// Since the segment is always horizontal, we don't need to store Y coordinate.
|
||||||
|
typedef struct event_list {
|
||||||
|
int32_t x;
|
||||||
|
int8_t type; // used internally, 1 for the left end (smaller X), -1 for the
|
||||||
|
// right end; pointless in output since the output segments
|
||||||
|
// are disjoint, therefore the types would always come in pairs
|
||||||
|
// and interchange (1 -1 1 -1 ...)
|
||||||
|
struct event_list* next;
|
||||||
|
} event_list;
|
||||||
|
|
||||||
|
// Mirrors all the clipping nodes of the tree relative to the y = x line.
|
||||||
|
void clip_tree_transpose(clip_node* root) {
|
||||||
|
if (root != NULL) {
|
||||||
|
if (root->type == CT_CLIP) {
|
||||||
|
double t = root->a;
|
||||||
|
root->a = root->b;
|
||||||
|
root->b = t;
|
||||||
|
}
|
||||||
|
clip_tree_transpose(root->l);
|
||||||
|
clip_tree_transpose(root->r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs a sequence of open-close events (types -1 and 1) for
|
||||||
|
// non-intersecting segments sorted by X coordinate.
|
||||||
|
// Combining nodes (AND, OR) may also accept sequences for intersecting
|
||||||
|
// segments, i.e. something like correct bracket sequences.
|
||||||
|
int clip_tree_do_clip(clip_node* root, int32_t x0, int32_t y, int32_t x1, event_list** ret) {
|
||||||
|
if (root == NULL) {
|
||||||
|
event_list* start = malloc(sizeof(event_list));
|
||||||
|
if (!start) {
|
||||||
ImagingError_MemoryError();
|
ImagingError_MemoryError();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
event_list* end = malloc(sizeof(event_list));
|
||||||
// Outer circle
|
if (!end) {
|
||||||
n = 0;
|
free(start);
|
||||||
for (i = start; i < end+1; i++) {
|
ImagingError_MemoryError();
|
||||||
if (i > end) {
|
return -1;
|
||||||
i = end;
|
}
|
||||||
|
start->x = x0;
|
||||||
|
start->type = 1;
|
||||||
|
start->next = end;
|
||||||
|
end->x = x1;
|
||||||
|
end->type = -1;
|
||||||
|
end->next = NULL;
|
||||||
|
*ret = start;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (root->type == CT_CLIP) {
|
||||||
|
double eps = 1e-9;
|
||||||
|
double A = root->a;
|
||||||
|
double B = root->b;
|
||||||
|
double C = root->c;
|
||||||
|
if (fabs(A) < eps) {
|
||||||
|
if (B * y + C < -eps) {
|
||||||
|
x0 = 1;
|
||||||
|
x1 = 0;
|
||||||
}
|
}
|
||||||
ellipsePoint(cx, cy, w, h, i, &x, &y);
|
|
||||||
if (i == start) {
|
|
||||||
sx = x, sy = y;
|
|
||||||
} else {
|
} else {
|
||||||
add_edge(&e[n++], lx, ly, x, y);
|
// X of intersection
|
||||||
|
double ix = - (B * y + C) / A;
|
||||||
|
if (A * x0 + B * y + C < eps) {
|
||||||
|
x0 = lround(fmax(x0, ix));
|
||||||
}
|
}
|
||||||
lx = x, ly = y;
|
if (A * x1 + B * y + C < eps) {
|
||||||
|
x1 = lround(fmin(x1, ix));
|
||||||
}
|
}
|
||||||
if (n == 0) {
|
}
|
||||||
|
if (x0 <= x1) {
|
||||||
|
event_list* start = malloc(sizeof(event_list));
|
||||||
|
if (!start) {
|
||||||
|
ImagingError_MemoryError();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
event_list* end = malloc(sizeof(event_list));
|
||||||
|
if (!end) {
|
||||||
|
free(start);
|
||||||
|
ImagingError_MemoryError();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
start->x = x0;
|
||||||
|
start->type = 1;
|
||||||
|
start->next = end;
|
||||||
|
end->x = x1;
|
||||||
|
end->type = -1;
|
||||||
|
end->next = NULL;
|
||||||
|
*ret = start;
|
||||||
|
} else {
|
||||||
|
*ret = NULL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (root->type == CT_OR || root->type == CT_AND) {
|
||||||
|
event_list* l1;
|
||||||
|
event_list* l2;
|
||||||
|
if (clip_tree_do_clip(root->l, x0, y, x1, &l1) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (clip_tree_do_clip(root->r, x0, y, x1, &l2) < 0) {
|
||||||
|
while (l1) {
|
||||||
|
l2 = l1->next;
|
||||||
|
free(l1);
|
||||||
|
l1 = l2;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*ret = NULL;
|
||||||
|
event_list* tail = NULL;
|
||||||
|
int32_t k1 = 0;
|
||||||
|
int32_t k2 = 0;
|
||||||
|
while (l1 != NULL || l2 != NULL) {
|
||||||
|
event_list* t;
|
||||||
|
if (l2 == NULL || (l1 != NULL && (l1->x < l2->x || (l1->x == l2->x && l1->type > l2->type)))) {
|
||||||
|
t = l1;
|
||||||
|
k1 += t->type;
|
||||||
|
assert(k1 >= 0);
|
||||||
|
l1 = l1->next;
|
||||||
|
} else {
|
||||||
|
t = l2;
|
||||||
|
k2 += t->type;
|
||||||
|
assert(k2 >= 0);
|
||||||
|
l2 = l2->next;
|
||||||
|
}
|
||||||
|
t->next = NULL;
|
||||||
|
if ((root->type == CT_OR && (
|
||||||
|
(t->type == 1 && (tail == NULL || tail->type == -1)) ||
|
||||||
|
(t->type == -1 && k1 == 0 && k2 == 0)
|
||||||
|
)) ||
|
||||||
|
(root->type == CT_AND && (
|
||||||
|
(t->type == 1 && (tail == NULL || tail->type == -1) && k1 > 0 && k2 > 0) ||
|
||||||
|
(t->type == -1 && tail != NULL && tail->type == 1 && (k1 == 0 || k2 == 0))
|
||||||
|
))) {
|
||||||
|
if (tail == NULL) {
|
||||||
|
*ret = t;
|
||||||
|
} else {
|
||||||
|
tail->next = t;
|
||||||
|
}
|
||||||
|
tail = t;
|
||||||
|
} else {
|
||||||
|
free(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*ret = NULL;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inner) {
|
// One more layer of processing on top of the regular ellipse.
|
||||||
// Inner circle
|
// Uses the clipping tree.
|
||||||
x0 += width - 1;
|
// Used for producing ellipse derivatives such as arc, chord, pie, etc.
|
||||||
y0 += width - 1;
|
typedef struct {
|
||||||
x1 -= width - 1;
|
ellipse_state st;
|
||||||
y1 -= width - 1;
|
clip_node* root;
|
||||||
|
clip_node nodes[7];
|
||||||
|
int32_t node_count;
|
||||||
|
event_list* head;
|
||||||
|
int32_t y;
|
||||||
|
} clip_ellipse_state;
|
||||||
|
|
||||||
w = x1 - x0;
|
typedef void (*clip_ellipse_init)(clip_ellipse_state*, int32_t, int32_t, int32_t, float, float);
|
||||||
h = y1 - y0;
|
|
||||||
if (w <= 0 || h <= 0) {
|
void debug_clip_tree(clip_node* root, int space) {
|
||||||
// ARC with no gap in the middle is a PIESLICE
|
if (root == NULL) {
|
||||||
mode = PIESLICE;
|
return;
|
||||||
inner = 0;
|
}
|
||||||
|
if (root->type == CT_CLIP) {
|
||||||
|
int t = space;
|
||||||
|
while (t--) {
|
||||||
|
fputc(' ', stderr);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "clip %+fx%+fy%+f > 0\n", root->a, root->b, root->c);
|
||||||
} else {
|
} else {
|
||||||
for (i = start; i < end+1; i++) {
|
debug_clip_tree(root->l, space + 2);
|
||||||
if (i > end) {
|
int t = space;
|
||||||
i = end;
|
while (t--) {
|
||||||
|
fputc(' ', stderr);
|
||||||
}
|
}
|
||||||
ellipsePoint(cx, cy, w, h, i, &x, &y);
|
fprintf(stderr, "%s\n", root->type == CT_AND ? "and" : "or");
|
||||||
if (i == start) {
|
debug_clip_tree(root->r, space + 2);
|
||||||
sx_inner = x, sy_inner = y;
|
}
|
||||||
|
if (space == 0) {
|
||||||
|
fputc('\n', stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resulting angles will satisfy 0 <= al < 360, al <= ar <= al + 360
|
||||||
|
void normalize_angles(float* al, float* ar) {
|
||||||
|
if (*ar - *al >= 360) {
|
||||||
|
*al = 0;
|
||||||
|
*ar = 360;
|
||||||
} else {
|
} else {
|
||||||
add_edge(&e[n++], lx_inner, ly_inner, x, y);
|
*al = fmod(*al < 0 ? 360 - (fmod(-*al, 360)) : *al, 360);
|
||||||
|
*ar = *al + fmod(*ar < *al ? 360 - fmod(*al - *ar, 360) : *ar - *al, 360);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An arc with caps orthogonal to the ellipse curve.
|
||||||
|
void arc_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) {
|
||||||
|
if (a < b) {
|
||||||
|
// transpose the coordinate system
|
||||||
|
arc_init(s, b, a, w, 90 - ar, 90 - al);
|
||||||
|
ellipse_init(&s->st, a, b, w);
|
||||||
|
clip_tree_transpose(s->root);
|
||||||
|
} else {
|
||||||
|
// a >= b, based on "wide" ellipse
|
||||||
|
ellipse_init(&s->st, a, b, w);
|
||||||
|
|
||||||
|
s->head = NULL;
|
||||||
|
s->node_count = 0;
|
||||||
|
normalize_angles(&al, &ar);
|
||||||
|
|
||||||
|
// building clipping tree, a lot of different cases
|
||||||
|
if (ar == al + 360) {
|
||||||
|
s->root = NULL;
|
||||||
|
} else {
|
||||||
|
clip_node* lc = s->nodes + s->node_count++;
|
||||||
|
clip_node* rc = s->nodes + s->node_count++;
|
||||||
|
lc->l = lc->r = rc->l = rc->r = NULL;
|
||||||
|
lc->type = rc->type = CT_CLIP;
|
||||||
|
lc->a = -a * sin(al * M_PI / 180.0);
|
||||||
|
lc->b = b * cos(al * M_PI / 180.0);
|
||||||
|
lc->c = (a * a - b * b) * sin(al * M_PI / 90.0) / 2.0;
|
||||||
|
rc->a = a * sin(ar * M_PI / 180.0);
|
||||||
|
rc->b = -b * cos(ar * M_PI / 180.0);
|
||||||
|
rc->c = (b * b - a * a) * sin(ar * M_PI / 90.0) / 2.0;
|
||||||
|
if (fmod(al, 180) == 0 || fmod(ar, 180) == 0) {
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->l = lc;
|
||||||
|
s->root->r = rc;
|
||||||
|
s->root->type = ar - al < 180 ? CT_AND : CT_OR;
|
||||||
|
} else if (((int)(al / 180) + (int)(ar / 180)) % 2 == 1) {
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->l = s->nodes + s->node_count++;
|
||||||
|
s->root->l->l = s->nodes + s->node_count++;
|
||||||
|
s->root->l->r = lc;
|
||||||
|
s->root->r = s->nodes + s->node_count++;
|
||||||
|
s->root->r->l = s->nodes + s->node_count++;
|
||||||
|
s->root->r->r = rc;
|
||||||
|
s->root->type = CT_OR;
|
||||||
|
s->root->l->type = CT_AND;
|
||||||
|
s->root->r->type = CT_AND;
|
||||||
|
s->root->l->l->type = CT_CLIP;
|
||||||
|
s->root->r->l->type = CT_CLIP;
|
||||||
|
s->root->l->l->l = s->root->l->l->r = NULL;
|
||||||
|
s->root->r->l->l = s->root->r->l->r = NULL;
|
||||||
|
s->root->l->l->a = s->root->l->l->c = 0;
|
||||||
|
s->root->r->l->a = s->root->r->l->c = 0;
|
||||||
|
s->root->l->l->b = (int)(al / 180) % 2 == 0 ? 1 : -1;
|
||||||
|
s->root->r->l->b = (int)(ar / 180) % 2 == 0 ? 1 : -1;
|
||||||
|
} else {
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->l = s->nodes + s->node_count++;
|
||||||
|
s->root->r = s->nodes + s->node_count++;
|
||||||
|
s->root->type = s->root->l->type = ar - al < 180 ? CT_AND : CT_OR;
|
||||||
|
s->root->l->l = lc;
|
||||||
|
s->root->l->r = rc;
|
||||||
|
s->root->r->type = CT_CLIP;
|
||||||
|
s->root->r->l = s->root->r->r = NULL;
|
||||||
|
s->root->r->a = s->root->r->c = 0;
|
||||||
|
s->root->r->b = ar < 180 || ar > 540 ? 1 : -1;
|
||||||
}
|
}
|
||||||
lx_inner = x, ly_inner = y;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end - start < 360) {
|
// A chord line.
|
||||||
// Close polygon
|
void chord_line_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) {
|
||||||
if (mode == PIESLICE) {
|
ellipse_init(&s->st, a, b, a + b + 1);
|
||||||
if (x != cx || y != cy) {
|
|
||||||
add_edge(&e[n++], sx, sy, cx, cy);
|
s->head = NULL;
|
||||||
add_edge(&e[n++], cx, cy, lx, ly);
|
s->node_count = 0;
|
||||||
if (inner) {
|
|
||||||
ImagingDrawWideLine(im, sx, sy, cx, cy, &ink, width, op);
|
// line equation for chord
|
||||||
ImagingDrawWideLine(im, cx, cy, lx, ly, &ink, width, op);
|
double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0);
|
||||||
|
double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0);
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->l = s->nodes + s->node_count++;
|
||||||
|
s->root->r = s->nodes + s->node_count++;
|
||||||
|
s->root->type = CT_AND;
|
||||||
|
s->root->l->type = s->root->r->type = CT_CLIP;
|
||||||
|
s->root->l->l = s->root->l->r = s->root->r->l = s->root->r->r = NULL;
|
||||||
|
s->root->l->a = yr - yl;
|
||||||
|
s->root->l->b = xl - xr;
|
||||||
|
s->root->l->c = -(s->root->l->a * xl + s->root->l->b * yl);
|
||||||
|
s->root->r->a = -s->root->l->a;
|
||||||
|
s->root->r->b = -s->root->l->b;
|
||||||
|
s->root->r->c = 2 * w * sqrt(pow(s->root->l->a, 2.0) + pow(s->root->l->b, 2.0)) - s->root->l->c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pie side.
|
||||||
|
void pie_side_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float _) {
|
||||||
|
ellipse_init(&s->st, a, b, a + b + 1);
|
||||||
|
|
||||||
|
s->head = NULL;
|
||||||
|
s->node_count = 0;
|
||||||
|
|
||||||
|
double xl = a * cos(al * M_PI / 180.0);
|
||||||
|
double yl = b * sin(al * M_PI / 180.0);
|
||||||
|
double a1 = -yl;
|
||||||
|
double b1 = xl;
|
||||||
|
double c1 = w * sqrt(a1 * a1 + b1 * b1);
|
||||||
|
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->type = CT_AND;
|
||||||
|
s->root->l = s->nodes + s->node_count++;
|
||||||
|
s->root->l->type = CT_AND;
|
||||||
|
|
||||||
|
clip_node* cnode;
|
||||||
|
cnode = s->nodes + s->node_count++;
|
||||||
|
cnode->l = cnode->r = NULL;
|
||||||
|
cnode->type = CT_CLIP;
|
||||||
|
cnode->a = a1;
|
||||||
|
cnode->b = b1;
|
||||||
|
cnode->c = c1;
|
||||||
|
s->root->l->l = cnode;
|
||||||
|
cnode = s->nodes + s->node_count++;
|
||||||
|
cnode->l = cnode->r = NULL;
|
||||||
|
cnode->type = CT_CLIP;
|
||||||
|
cnode->a = -a1;
|
||||||
|
cnode->b = -b1;
|
||||||
|
cnode->c = c1;
|
||||||
|
s->root->l->r = cnode;
|
||||||
|
cnode = s->nodes + s->node_count++;
|
||||||
|
cnode->l = cnode->r = NULL;
|
||||||
|
cnode->type = CT_CLIP;
|
||||||
|
cnode->a = b1;
|
||||||
|
cnode->b = -a1;
|
||||||
|
cnode->c = 0;
|
||||||
|
s->root->r = cnode;
|
||||||
}
|
}
|
||||||
} else if (mode == CHORD) {
|
|
||||||
add_edge(&e[n++], sx, sy, lx, ly);
|
// A chord.
|
||||||
if (inner) {
|
void chord_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) {
|
||||||
add_edge(&e[n++], sx_inner, sy_inner, lx_inner, ly_inner);
|
ellipse_init(&s->st, a, b, w);
|
||||||
|
|
||||||
|
s->head = NULL;
|
||||||
|
s->node_count = 0;
|
||||||
|
|
||||||
|
// line equation for chord
|
||||||
|
double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0);
|
||||||
|
double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0);
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->l = s->root->r = NULL;
|
||||||
|
s->root->type = CT_CLIP;
|
||||||
|
s->root->a = yr - yl;
|
||||||
|
s->root->b = xl - xr;
|
||||||
|
s->root->c = -(s->root->a * xl + s->root->b * yl);
|
||||||
}
|
}
|
||||||
} else if (mode == ARC) {
|
|
||||||
add_edge(&e[n++], sx, sy, sx_inner, sy_inner);
|
// A pie. Can also be used to draw an arc with ugly sharp caps.
|
||||||
add_edge(&e[n++], lx, ly, lx_inner, ly_inner);
|
void pie_init(clip_ellipse_state* s, int32_t a, int32_t b, int32_t w, float al, float ar) {
|
||||||
|
ellipse_init(&s->st, a, b, w);
|
||||||
|
|
||||||
|
s->head = NULL;
|
||||||
|
s->node_count = 0;
|
||||||
|
|
||||||
|
// line equations for pie sides
|
||||||
|
double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0);
|
||||||
|
double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0);
|
||||||
|
|
||||||
|
clip_node* lc = s->nodes + s->node_count++;
|
||||||
|
clip_node* rc = s->nodes + s->node_count++;
|
||||||
|
lc->l = lc->r = rc->l = rc->r = NULL;
|
||||||
|
lc->type = rc->type = CT_CLIP;
|
||||||
|
lc->a = -yl;
|
||||||
|
lc->b = xl;
|
||||||
|
lc->c = 0;
|
||||||
|
rc->a = yr;
|
||||||
|
rc->b = -xr;
|
||||||
|
rc->c = 0;
|
||||||
|
|
||||||
|
s->root = s->nodes + s->node_count++;
|
||||||
|
s->root->l = lc;
|
||||||
|
s->root->r = rc;
|
||||||
|
s->root->type = ar - al < 180 ? CT_AND : CT_OR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clip_ellipse_free(clip_ellipse_state* s) {
|
||||||
|
while (s->head != NULL) {
|
||||||
|
event_list* t = s->head;
|
||||||
|
s->head = s->head->next;
|
||||||
|
free(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw->polygon(im, n, e, ink, 0);
|
int8_t clip_ellipse_next(clip_ellipse_state* s, int32_t* ret_x0, int32_t* ret_y, int32_t* ret_x1) {
|
||||||
|
int32_t x0, y, x1;
|
||||||
free(e);
|
while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) {
|
||||||
|
if (clip_tree_do_clip(s->root, x0, y, x1, &s->head) < 0) {
|
||||||
|
return -2;
|
||||||
}
|
}
|
||||||
|
s->y = y;
|
||||||
|
}
|
||||||
|
if (s->head != NULL) {
|
||||||
|
*ret_y = s->y;
|
||||||
|
event_list* t = s->head;
|
||||||
|
s->head = s->head->next;
|
||||||
|
*ret_x0 = t->x;
|
||||||
|
free(t);
|
||||||
|
t = s->head;
|
||||||
|
assert(t != NULL);
|
||||||
|
s->head = s->head->next;
|
||||||
|
*ret_x1 = t->x;
|
||||||
|
free(t);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
int
|
|
||||||
ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1,
|
|
||||||
float start, float end, const void* ink, int width, int op)
|
|
||||||
{
|
|
||||||
return ellipse(im, x0, y0, x1, y1, start, end, ink, 0, width, ARC, op);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1,
|
ellipseNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
float start, float end, const void* ink, int fill,
|
const void* ink_, int fill,
|
||||||
int width, int op)
|
int width, int op)
|
||||||
{
|
{
|
||||||
return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, CHORD, op);
|
DRAW* draw;
|
||||||
|
INT32 ink;
|
||||||
|
DRAWINIT();
|
||||||
|
|
||||||
|
int a = x1 - x0;
|
||||||
|
int b = y1 - y0;
|
||||||
|
if (a < 0 || b < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fill) {
|
||||||
|
width = a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
ellipse_state st;
|
||||||
|
ellipse_init(&st, a, b, width);
|
||||||
|
int32_t X0, Y, X1;
|
||||||
|
while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
|
||||||
|
draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
clipEllipseNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end,
|
||||||
|
const void* ink_, int width, int op, clip_ellipse_init init)
|
||||||
|
{
|
||||||
|
DRAW* draw;
|
||||||
|
INT32 ink;
|
||||||
|
DRAWINIT();
|
||||||
|
|
||||||
|
int a = x1 - x0;
|
||||||
|
int b = y1 - y0;
|
||||||
|
if (a < 0 || b < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
clip_ellipse_state st;
|
||||||
|
init(&st, a, b, width, start, end);
|
||||||
|
// debug_clip_tree(st.root, 0);
|
||||||
|
int32_t X0, Y, X1;
|
||||||
|
int next_code;
|
||||||
|
while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) {
|
||||||
|
draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
|
||||||
|
}
|
||||||
|
clip_ellipse_free(&st);
|
||||||
|
return next_code == -1 ? 0 : -1;
|
||||||
|
}
|
||||||
|
static int
|
||||||
|
arcNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end,
|
||||||
|
const void* ink_, int width, int op)
|
||||||
|
{
|
||||||
|
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, arc_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
chordNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end,
|
||||||
|
const void* ink_, int width, int op)
|
||||||
|
{
|
||||||
|
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
chordLineNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end,
|
||||||
|
const void* ink_, int width, int op)
|
||||||
|
{
|
||||||
|
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pieNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end,
|
||||||
|
const void* ink_, int width, int op)
|
||||||
|
{
|
||||||
|
return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, pie_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pieSideNew(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start,
|
||||||
|
const void* ink_, int width, int op)
|
||||||
|
{
|
||||||
|
return clipEllipseNew(im, x0, y0, x1, y1, start, 0, ink_, width, op, pie_side_init);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1,
|
ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
const void* ink, int fill, int width, int op)
|
const void* ink, int fill, int width, int op)
|
||||||
{
|
{
|
||||||
return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, width, CHORD, op);
|
return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end, const void* ink, int width, int op)
|
||||||
|
{
|
||||||
|
normalize_angles(&start, &end);
|
||||||
|
if (start + 360 == end) {
|
||||||
|
return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op);
|
||||||
|
}
|
||||||
|
if (start == end) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return arcNew(im, x0, y0, x1, y1, start, end, ink, width, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
|
float start, float end, const void* ink, int fill,
|
||||||
|
int width, int op)
|
||||||
|
{
|
||||||
|
normalize_angles(&start, &end);
|
||||||
|
if (start + 360 == end) {
|
||||||
|
return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op);
|
||||||
|
}
|
||||||
|
if (start == end) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fill) {
|
||||||
|
return chordNew(im, x0, y0, x1, y1, start, end, ink, x1 - x0 + y1 - y0 + 1, op);
|
||||||
|
} else {
|
||||||
|
if (chordLineNew(im, x0, y0, x1, y1, start, end, ink, width, op)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return chordNew(im, x0, y0, x1, y1, start, end, ink, width, op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1,
|
ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1,
|
||||||
float start, float end, const void* ink, int fill,
|
float start, float end, const void* ink, int fill,
|
||||||
int width, int op)
|
int width, int op)
|
||||||
{
|
{
|
||||||
return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, PIESLICE, op);
|
normalize_angles(&start, &end);
|
||||||
|
if (start + 360 == end) {
|
||||||
|
return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op);
|
||||||
}
|
}
|
||||||
|
if (start == end) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fill) {
|
||||||
|
return pieNew(im, x0, y0, x1, y1, start, end, ink, x1 + y1 - x0 - y0, op);
|
||||||
|
} else {
|
||||||
|
if (pieSideNew(im, x0, y0, x1, y1, start, ink, width, op)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pieSideNew(im, x0, y0, x1, y1, end, ink, width, op)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int xc = lround((x0 + x1 - width) / 2.0), yc = lround((y0 + y1 - width) / 2.0);
|
||||||
|
ellipseNew(im, xc, yc, xc + width - 1, yc + width - 1, ink, 1, 0, op);
|
||||||
|
return pieNew(im, x0, y0, x1, y1, start, end, ink, width, op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,20 @@ deps = {
|
||||||
],
|
],
|
||||||
"libs": [r"output\release-static\{architecture}\lib\*.lib"],
|
"libs": [r"output\release-static\{architecture}\lib\*.lib"],
|
||||||
},
|
},
|
||||||
|
"libpng": {
|
||||||
|
"url": SF_MIRROR + "/project/libpng/libpng16/1.6.37/lpng1637.zip",
|
||||||
|
"filename": "lpng1637.zip",
|
||||||
|
"dir": "lpng1637",
|
||||||
|
"build": [
|
||||||
|
# lint: do not inline
|
||||||
|
cmd_cmake(("-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF")),
|
||||||
|
cmd_nmake(target="clean"),
|
||||||
|
cmd_nmake(),
|
||||||
|
cmd_copy("libpng16_static.lib", "libpng16.lib"),
|
||||||
|
],
|
||||||
|
"headers": [r"png*.h"],
|
||||||
|
"libs": [r"libpng16.lib"],
|
||||||
|
},
|
||||||
"freetype": {
|
"freetype": {
|
||||||
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.2.tar.gz", # noqa: E501
|
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.2.tar.gz", # noqa: E501
|
||||||
"filename": "freetype-2.10.2.tar.gz",
|
"filename": "freetype-2.10.2.tar.gz",
|
||||||
|
@ -181,8 +195,10 @@ deps = {
|
||||||
'<PropertyGroup Label="Globals">': '<PropertyGroup Label="Globals">\n <WindowsTargetPlatformVersion>$(WindowsSDKVersion)</WindowsTargetPlatformVersion>', # noqa: E501
|
'<PropertyGroup Label="Globals">': '<PropertyGroup Label="Globals">\n <WindowsTargetPlatformVersion>$(WindowsSDKVersion)</WindowsTargetPlatformVersion>', # noqa: E501
|
||||||
},
|
},
|
||||||
r"builds\windows\vc2010\freetype.user.props": {
|
r"builds\windows\vc2010\freetype.user.props": {
|
||||||
"<UserDefines></UserDefines>": "<UserDefines>FT_CONFIG_OPTION_USE_HARFBUZZ</UserDefines>", # noqa: E501
|
"<UserDefines></UserDefines>": "<UserDefines>FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ</UserDefines>", # noqa: E501
|
||||||
"<UserIncludeDirectories></UserIncludeDirectories>": r"<UserIncludeDirectories>{dir_harfbuzz}\src</UserIncludeDirectories>", # noqa: E501
|
"<UserIncludeDirectories></UserIncludeDirectories>": r"<UserIncludeDirectories>{dir_harfbuzz}\src;{inc_dir}</UserIncludeDirectories>", # noqa: E501
|
||||||
|
"<UserLibraryDirectories></UserLibraryDirectories>": "<UserLibraryDirectories>{lib_dir}</UserLibraryDirectories>", # noqa: E501
|
||||||
|
"<UserDependencies></UserDependencies>": "<UserDependencies>zlib.lib;libpng16.lib</UserDependencies>", # noqa: E501
|
||||||
},
|
},
|
||||||
r"src/autofit/afshaper.c": {
|
r"src/autofit/afshaper.c": {
|
||||||
# link against harfbuzz.lib once it becomes available
|
# link against harfbuzz.lib once it becomes available
|
||||||
|
|