mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-03 21:24:31 +03:00
Merge remote-tracking branch 'upstream/master' into pytest
This commit is contained in:
commit
9087599e60
|
@ -5,6 +5,9 @@ Changelog (Pillow)
|
||||||
7.1.0 (unreleased)
|
7.1.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Only draw each polygon pixel once #4333
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Add support for shooting situation Exif IFD tags #4398
|
- Add support for shooting situation Exif IFD tags #4398
|
||||||
[alexagv]
|
[alexagv]
|
||||||
|
|
||||||
|
|
|
@ -61,15 +61,19 @@ To report a security vulnerability, please follow the procedure described in the
|
||||||
:alt: AppVeyor CI build status (Windows)
|
:alt: AppVeyor CI build status (Windows)
|
||||||
|
|
||||||
.. |gha_lint| image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
|
.. |gha_lint| image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
|
||||||
|
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint
|
||||||
:alt: GitHub Actions build status (Lint)
|
:alt: GitHub Actions build status (Lint)
|
||||||
|
|
||||||
.. |gha_docker| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg
|
.. |gha_docker| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg
|
||||||
|
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22
|
||||||
:alt: GitHub Actions build status (Test Docker)
|
:alt: GitHub Actions build status (Test Docker)
|
||||||
|
|
||||||
.. |gha| image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg
|
.. |gha| image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg
|
||||||
|
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest
|
||||||
:alt: GitHub Actions build status (Test Linux and macOS)
|
:alt: GitHub Actions build status (Test Linux and macOS)
|
||||||
|
|
||||||
.. |gha_windows| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg
|
.. |gha_windows| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg
|
||||||
|
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22
|
||||||
:alt: GitHub Actions build status (Test Windows)
|
:alt: GitHub Actions build status (Test Windows)
|
||||||
|
|
||||||
.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
|
.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Main Release
|
## Main Release
|
||||||
|
|
||||||
Released quarterly on the first day of January, April, July, October.
|
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
|
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `master` branch.
|
* [ ] Develop and prepare release in `master` branch.
|
||||||
|
|
|
@ -4,6 +4,7 @@ Helper functions.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -191,9 +192,12 @@ class PillowTestCase(unittest.TestCase):
|
||||||
raise OSError()
|
raise OSError()
|
||||||
|
|
||||||
outfile = self.tempfile("temp.png")
|
outfile = self.tempfile("temp.png")
|
||||||
if command_succeeds([IMCONVERT, f, outfile]):
|
rc = subprocess.call(
|
||||||
return Image.open(outfile)
|
[IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
|
||||||
raise OSError()
|
)
|
||||||
|
if rc:
|
||||||
|
raise OSError
|
||||||
|
return Image.open(outfile)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS")
|
@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS")
|
||||||
|
@ -268,34 +272,20 @@ def hopper(mode=None, cache={}):
|
||||||
return im.copy()
|
return im.copy()
|
||||||
|
|
||||||
|
|
||||||
def command_succeeds(cmd):
|
|
||||||
"""
|
|
||||||
Runs the command, which must be a list of strings. Returns True if the
|
|
||||||
command succeeds, or False if an OSError was raised by subprocess.Popen.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
||||||
except OSError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available():
|
def djpeg_available():
|
||||||
return command_succeeds(["djpeg", "-version"])
|
return bool(shutil.which("djpeg"))
|
||||||
|
|
||||||
|
|
||||||
def cjpeg_available():
|
def cjpeg_available():
|
||||||
return command_succeeds(["cjpeg", "-version"])
|
return bool(shutil.which("cjpeg"))
|
||||||
|
|
||||||
|
|
||||||
def netpbm_available():
|
def netpbm_available():
|
||||||
return command_succeeds(["ppmquant", "--version"]) and command_succeeds(
|
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||||
["ppmtogif", "--version"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def imagemagick_available():
|
def imagemagick_available():
|
||||||
return IMCONVERT and command_succeeds([IMCONVERT, "-version"])
|
return bool(IMCONVERT and shutil.which(IMCONVERT))
|
||||||
|
|
||||||
|
|
||||||
def on_appveyor():
|
def on_appveyor():
|
||||||
|
|
BIN
Tests/images/imagedraw_chord_zero_width.png
Normal file
BIN
Tests/images/imagedraw_chord_zero_width.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 427 B |
BIN
Tests/images/imagedraw_ellipse_translucent.png
Normal file
BIN
Tests/images/imagedraw_ellipse_translucent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 390 B |
BIN
Tests/images/imagedraw_ellipse_zero_width.png
Normal file
BIN
Tests/images/imagedraw_ellipse_zero_width.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 339 B |
BIN
Tests/images/imagedraw_pieslice_zero_width.png
Normal file
BIN
Tests/images/imagedraw_pieslice_zero_width.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 403 B |
BIN
Tests/images/imagedraw_rectangle_zero_width.png
Normal file
BIN
Tests/images/imagedraw_rectangle_zero_width.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
|
@ -20,7 +20,7 @@ def test_isatty():
|
||||||
def test_seek_mode_0():
|
def test_seek_mode_0():
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = 0
|
mode = 0
|
||||||
with open(TEST_FILE) as fh:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -34,7 +34,7 @@ def test_seek_mode_0():
|
||||||
def test_seek_mode_1():
|
def test_seek_mode_1():
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = 1
|
mode = 1
|
||||||
with open(TEST_FILE) as fh:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -48,7 +48,7 @@ def test_seek_mode_1():
|
||||||
def test_seek_mode_2():
|
def test_seek_mode_2():
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = 2
|
mode = 2
|
||||||
with open(TEST_FILE) as fh:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -61,73 +61,87 @@ def test_seek_mode_2():
|
||||||
|
|
||||||
def test_read_n0():
|
def test_read_n0():
|
||||||
# Arrange
|
# Arrange
|
||||||
with open(TEST_FILE) as fh:
|
for bytesmode in (True, False):
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(81)
|
container.seek(81)
|
||||||
data = container.read()
|
data = container.read()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert data == "7\nThis is line 8\n"
|
if bytesmode:
|
||||||
|
data = data.decode()
|
||||||
|
assert data == "7\nThis is line 8\n"
|
||||||
|
|
||||||
|
|
||||||
def test_read_n():
|
def test_read_n():
|
||||||
# Arrange
|
# Arrange
|
||||||
with open(TEST_FILE) as fh:
|
for bytesmode in (True, False):
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(81)
|
container.seek(81)
|
||||||
data = container.read(3)
|
data = container.read(3)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert data == "7\nT"
|
if bytesmode:
|
||||||
|
data = data.decode()
|
||||||
|
assert data == "7\nT"
|
||||||
|
|
||||||
|
|
||||||
def test_read_eof():
|
def test_read_eof():
|
||||||
# Arrange
|
# Arrange
|
||||||
with open(TEST_FILE) as fh:
|
for bytesmode in (True, False):
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(100)
|
container.seek(100)
|
||||||
data = container.read()
|
data = container.read()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert data == ""
|
if bytesmode:
|
||||||
|
data = data.decode()
|
||||||
|
assert data == ""
|
||||||
|
|
||||||
|
|
||||||
def test_readline():
|
def test_readline():
|
||||||
# Arrange
|
# Arrange
|
||||||
with open(TEST_FILE) as fh:
|
for bytesmode in (True, False):
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
data = container.readline()
|
data = container.readline()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert data == "This is line 1\n"
|
if bytesmode:
|
||||||
|
data = data.decode()
|
||||||
|
assert data == "This is line 1\n"
|
||||||
|
|
||||||
|
|
||||||
def test_readlines():
|
def test_readlines():
|
||||||
# Arrange
|
# Arrange
|
||||||
expected = [
|
for bytesmode in (True, False):
|
||||||
"This is line 1\n",
|
expected = [
|
||||||
"This is line 2\n",
|
"This is line 1\n",
|
||||||
"This is line 3\n",
|
"This is line 2\n",
|
||||||
"This is line 4\n",
|
"This is line 3\n",
|
||||||
"This is line 5\n",
|
"This is line 4\n",
|
||||||
"This is line 6\n",
|
"This is line 5\n",
|
||||||
"This is line 7\n",
|
"This is line 6\n",
|
||||||
"This is line 8\n",
|
"This is line 7\n",
|
||||||
]
|
"This is line 8\n",
|
||||||
with open(TEST_FILE) as fh:
|
]
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
data = container.readlines()
|
data = container.readlines()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
if bytesmode:
|
||||||
assert data == expected
|
data = [line.decode() for line in data]
|
||||||
|
assert data == expected
|
||||||
|
|
|
@ -228,6 +228,19 @@ def test_chord_width_fill():
|
||||||
assert_image_similar(im, Image.open(expected), 1)
|
assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_chord_zero_width():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=0)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
with Image.open("Tests/images/imagedraw_chord_zero_width.png") as expected:
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def helper_ellipse(mode, bbox):
|
def helper_ellipse(mode, bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
|
@ -310,6 +323,19 @@ def test_ellipse_width_fill():
|
||||||
assert_image_similar(im, Image.open(expected), 1)
|
assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ellipse_zero_width():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.ellipse(BBOX1, fill="green", outline="blue", width=0)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
with Image.open("Tests/images/imagedraw_ellipse_zero_width.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,6 +446,19 @@ def test_pieslice_width_fill():
|
||||||
assert_image_similar(im, Image.open(expected), 1)
|
assert_image_similar(im, Image.open(expected), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pieslice_zero_width():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=0)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
with Image.open("Tests/images/imagedraw_pieslice_zero_width.png") as expected:
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def helper_point(points):
|
def helper_point(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -537,6 +576,19 @@ def test_rectangle_width_fill():
|
||||||
assert_image_equal(im, Image.open(expected))
|
assert_image_equal(im, Image.open(expected))
|
||||||
|
|
||||||
|
|
||||||
|
def test_rectangle_zero_width():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.rectangle(BBOX1, fill="blue", outline="green", width=0)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
with Image.open("Tests/images/imagedraw_rectangle_zero_width.png") as expected:
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle_I16():
|
def test_rectangle_I16():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("I;16", (W, H))
|
im = Image.new("I;16", (W, H))
|
||||||
|
|
|
@ -154,7 +154,7 @@ Methods
|
||||||
To paste pixel data into an image, use the
|
To paste pixel data into an image, use the
|
||||||
:py:meth:`~PIL.Image.Image.paste` method on the image itself.
|
:py:meth:`~PIL.Image.Image.paste` method on the image itself.
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=0)
|
.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1)
|
||||||
|
|
||||||
Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points
|
Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points
|
||||||
with a straight line.
|
with a straight line.
|
||||||
|
@ -168,7 +168,7 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 5.3.0
|
.. versionadded:: 5.3.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=0)
|
.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=1)
|
||||||
|
|
||||||
Draws an ellipse inside the given bounding box.
|
Draws an ellipse inside the given bounding box.
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 5.3.0
|
.. versionadded:: 5.3.0
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=0)
|
.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1)
|
||||||
|
|
||||||
Same as arc, but also draws straight lines between the end points and the
|
Same as arc, but also draws straight lines between the end points and the
|
||||||
center of the bounding box.
|
center of the bounding box.
|
||||||
|
@ -236,7 +236,7 @@ Methods
|
||||||
:param outline: Color to use for the outline.
|
:param outline: Color to use for the outline.
|
||||||
:param fill: Color to use for the fill.
|
:param fill: Color to use for the fill.
|
||||||
|
|
||||||
.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=0)
|
.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=1)
|
||||||
|
|
||||||
Draws a rectangle.
|
Draws a rectangle.
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ class ContainerIO:
|
||||||
else:
|
else:
|
||||||
n = self.length - self.pos
|
n = self.length - self.pos
|
||||||
if not n: # EOF
|
if not n: # EOF
|
||||||
return ""
|
return b"" if "b" in self.fh.mode else ""
|
||||||
self.pos = self.pos + n
|
self.pos = self.pos + n
|
||||||
return self.fh.read(n)
|
return self.fh.read(n)
|
||||||
|
|
||||||
|
@ -92,13 +92,14 @@ class ContainerIO:
|
||||||
|
|
||||||
:returns: An 8-bit string.
|
:returns: An 8-bit string.
|
||||||
"""
|
"""
|
||||||
s = ""
|
s = b"" if "b" in self.fh.mode else ""
|
||||||
|
newline_character = b"\n" if "b" in self.fh.mode else "\n"
|
||||||
while True:
|
while True:
|
||||||
c = self.read(1)
|
c = self.read(1)
|
||||||
if not c:
|
if not c:
|
||||||
break
|
break
|
||||||
s = s + c
|
s = s + c
|
||||||
if c == "\n":
|
if c == newline_character:
|
||||||
break
|
break
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
|
@ -134,20 +134,20 @@ class ImageDraw:
|
||||||
if ink is not None:
|
if ink is not None:
|
||||||
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
self.draw.draw_bitmap(xy, bitmap.im, ink)
|
||||||
|
|
||||||
def chord(self, xy, start, end, fill=None, outline=None, width=0):
|
def chord(self, xy, start, end, fill=None, outline=None, width=1):
|
||||||
"""Draw a chord."""
|
"""Draw a chord."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_chord(xy, start, end, fill, 1)
|
self.draw.draw_chord(xy, start, end, fill, 1)
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill and width != 0:
|
||||||
self.draw.draw_chord(xy, start, end, ink, 0, width)
|
self.draw.draw_chord(xy, start, end, ink, 0, width)
|
||||||
|
|
||||||
def ellipse(self, xy, fill=None, outline=None, width=0):
|
def ellipse(self, xy, fill=None, outline=None, width=1):
|
||||||
"""Draw an ellipse."""
|
"""Draw an ellipse."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_ellipse(xy, fill, 1)
|
self.draw.draw_ellipse(xy, fill, 1)
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill and width != 0:
|
||||||
self.draw.draw_ellipse(xy, ink, 0, width)
|
self.draw.draw_ellipse(xy, ink, 0, width)
|
||||||
|
|
||||||
def line(self, xy, fill=None, width=0, joint=None):
|
def line(self, xy, fill=None, width=0, joint=None):
|
||||||
|
@ -219,12 +219,12 @@ class ImageDraw:
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill:
|
||||||
self.draw.draw_outline(shape, ink, 0)
|
self.draw.draw_outline(shape, ink, 0)
|
||||||
|
|
||||||
def pieslice(self, xy, start, end, fill=None, outline=None, width=0):
|
def pieslice(self, xy, start, end, fill=None, outline=None, width=1):
|
||||||
"""Draw a pieslice."""
|
"""Draw a pieslice."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_pieslice(xy, start, end, fill, 1)
|
self.draw.draw_pieslice(xy, start, end, fill, 1)
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill and width != 0:
|
||||||
self.draw.draw_pieslice(xy, start, end, ink, 0, width)
|
self.draw.draw_pieslice(xy, start, end, ink, 0, width)
|
||||||
|
|
||||||
def point(self, xy, fill=None):
|
def point(self, xy, fill=None):
|
||||||
|
@ -241,12 +241,12 @@ class ImageDraw:
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill:
|
||||||
self.draw.draw_polygon(xy, ink, 0)
|
self.draw.draw_polygon(xy, ink, 0)
|
||||||
|
|
||||||
def rectangle(self, xy, fill=None, outline=None, width=0):
|
def rectangle(self, xy, fill=None, outline=None, width=1):
|
||||||
"""Draw a rectangle."""
|
"""Draw a rectangle."""
|
||||||
ink, fill = self._getink(outline, fill)
|
ink, fill = self._getink(outline, fill)
|
||||||
if fill is not None:
|
if fill is not None:
|
||||||
self.draw.draw_rectangle(xy, fill, 1)
|
self.draw.draw_rectangle(xy, fill, 1)
|
||||||
if ink is not None and ink != fill:
|
if ink is not None and ink != fill and width != 0:
|
||||||
self.draw.draw_rectangle(xy, ink, 0, width)
|
self.draw.draw_rectangle(xy, ink, 0, width)
|
||||||
|
|
||||||
def _multiline_check(self, text):
|
def _multiline_check(self, text):
|
||||||
|
|
|
@ -415,6 +415,35 @@ x_cmp(const void *x0, const void *x1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
draw_horizontal_lines(Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
if (e[i].ymin == y && e[i].ymin == e[i].ymax) {
|
||||||
|
int xmax;
|
||||||
|
int xmin = e[i].xmin;
|
||||||
|
if (*x_pos < xmin) {
|
||||||
|
// Line would be after the current position
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
xmax = e[i].xmax;
|
||||||
|
if (*x_pos > xmin) {
|
||||||
|
// Line would be partway through x_pos, so increase the starting point
|
||||||
|
xmin = *x_pos;
|
||||||
|
if (xmax < xmin) {
|
||||||
|
// Line would now end before it started
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(*hline)(im, xmin, e[i].ymin, xmax, ink);
|
||||||
|
*x_pos = xmax+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Filled polygon draw function using scan line algorithm.
|
* Filled polygon draw function using scan line algorithm.
|
||||||
*/
|
*/
|
||||||
|
@ -442,10 +471,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
/* This causes the pixels of horizontal edges to be drawn twice :(
|
|
||||||
* but without it there are inconsistencies in ellipses */
|
|
||||||
if (e[i].ymin == e[i].ymax) {
|
if (e[i].ymin == e[i].ymax) {
|
||||||
(*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ymin > e[i].ymin) {
|
if (ymin > e[i].ymin) {
|
||||||
|
@ -472,6 +498,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill,
|
||||||
}
|
}
|
||||||
for (; ymin <= ymax; ymin++) {
|
for (; ymin <= ymax; ymin++) {
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
int x_pos = 0;
|
||||||
for (i = 0; i < edge_count; i++) {
|
for (i = 0; i < edge_count; i++) {
|
||||||
Edge* current = edge_table[i];
|
Edge* current = edge_table[i];
|
||||||
if (ymin >= current->ymin && ymin <= current->ymax) {
|
if (ymin >= current->ymin && ymin <= current->ymax) {
|
||||||
|
@ -485,8 +512,30 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill,
|
||||||
}
|
}
|
||||||
qsort(xx, j, sizeof(float), x_cmp);
|
qsort(xx, j, sizeof(float), x_cmp);
|
||||||
for (i = 1; i < j; i += 2) {
|
for (i = 1; i < j; i += 2) {
|
||||||
(*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink);
|
int x_end = ROUND_DOWN(xx[i]);
|
||||||
|
if (x_end < x_pos) {
|
||||||
|
// Line would be before the current position
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
|
||||||
|
if (x_end < x_pos) {
|
||||||
|
// Line would be before the current position
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x_start = ROUND_UP(xx[i-1]);
|
||||||
|
if (x_pos > x_start) {
|
||||||
|
// Line would be partway through x_pos, so increase the starting point
|
||||||
|
x_start = x_pos;
|
||||||
|
if (x_end < x_start) {
|
||||||
|
// Line would now end before it started
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*hline)(im, x_start, ymin, x_end, ink);
|
||||||
|
x_pos = x_end+1;
|
||||||
}
|
}
|
||||||
|
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(xx);
|
free(xx);
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
|
|
||||||
struct _Heap {
|
struct _Heap {
|
||||||
void **heap;
|
void **heap;
|
||||||
int heapsize;
|
unsigned int heapsize;
|
||||||
int heapcount;
|
unsigned int heapcount;
|
||||||
HeapCmpFunc cf;
|
HeapCmpFunc cf;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ void ImagingQuantHeapFree(Heap *h) {
|
||||||
free(h);
|
free(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _heap_grow(Heap *h,int newsize) {
|
static int _heap_grow(Heap *h,unsigned int newsize) {
|
||||||
void *newheap;
|
void *newheap;
|
||||||
if (!newsize) newsize=h->heapsize<<1;
|
if (!newsize) newsize=h->heapsize<<1;
|
||||||
if (newsize<h->heapsize) return 0;
|
if (newsize<h->heapsize) return 0;
|
||||||
|
@ -64,7 +64,7 @@ static int _heap_grow(Heap *h,int newsize) {
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
static int _heap_test(Heap *h) {
|
static int _heap_test(Heap *h) {
|
||||||
int k;
|
unsigned int k;
|
||||||
for (k=1;k*2<=h->heapcount;k++) {
|
for (k=1;k*2<=h->heapcount;k++) {
|
||||||
if (h->cf(h,h->heap[k],h->heap[k*2])<0) {
|
if (h->cf(h,h->heap[k],h->heap[k*2])<0) {
|
||||||
printf ("heap is bad\n");
|
printf ("heap is bad\n");
|
||||||
|
@ -80,7 +80,7 @@ static int _heap_test(Heap *h) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int ImagingQuantHeapRemove(Heap* h,void **r) {
|
int ImagingQuantHeapRemove(Heap* h,void **r) {
|
||||||
int k,l;
|
unsigned int k,l;
|
||||||
void *v;
|
void *v;
|
||||||
|
|
||||||
if (!h->heapcount) {
|
if (!h->heapcount) {
|
||||||
|
|
|
@ -44,7 +44,7 @@ typedef struct _ColorCube{
|
||||||
unsigned int rWidth, gWidth, bWidth, aWidth;
|
unsigned int rWidth, gWidth, bWidth, aWidth;
|
||||||
unsigned int rOffset, gOffset, bOffset, aOffset;
|
unsigned int rOffset, gOffset, bOffset, aOffset;
|
||||||
|
|
||||||
long size;
|
unsigned long size;
|
||||||
ColorBucket buckets;
|
ColorBucket buckets;
|
||||||
} *ColorCube;
|
} *ColorCube;
|
||||||
|
|
||||||
|
@ -134,10 +134,10 @@ add_color_to_color_cube(const ColorCube cube, const Pixel *p) {
|
||||||
bucket->a += p->c.a;
|
bucket->a += p->c.a;
|
||||||
}
|
}
|
||||||
|
|
||||||
static long
|
static unsigned long
|
||||||
count_used_color_buckets(const ColorCube cube) {
|
count_used_color_buckets(const ColorCube cube) {
|
||||||
long usedBuckets = 0;
|
unsigned long usedBuckets = 0;
|
||||||
long i;
|
unsigned long i;
|
||||||
for (i=0; i < cube->size; i++) {
|
for (i=0; i < cube->size; i++) {
|
||||||
if (cube->buckets[i].count > 0) {
|
if (cube->buckets[i].count > 0) {
|
||||||
usedBuckets += 1;
|
usedBuckets += 1;
|
||||||
|
@ -194,7 +194,7 @@ void add_bucket_values(ColorBucket src, ColorBucket dst) {
|
||||||
|
|
||||||
/* expand or shrink a given cube to level */
|
/* expand or shrink a given cube to level */
|
||||||
static ColorCube copy_color_cube(const ColorCube cube,
|
static ColorCube copy_color_cube(const ColorCube cube,
|
||||||
int rBits, int gBits, int bBits, int aBits)
|
unsigned int rBits, unsigned int gBits, unsigned int bBits, unsigned int aBits)
|
||||||
{
|
{
|
||||||
unsigned int r, g, b, a;
|
unsigned int r, g, b, a;
|
||||||
long src_pos, dst_pos;
|
long src_pos, dst_pos;
|
||||||
|
@ -302,7 +302,7 @@ void add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorBucket
|
ColorBucket
|
||||||
combined_palette(ColorBucket bucketsA, long nBucketsA, ColorBucket bucketsB, long nBucketsB) {
|
combined_palette(ColorBucket bucketsA, unsigned long nBucketsA, ColorBucket bucketsB, unsigned long nBucketsB) {
|
||||||
ColorBucket result;
|
ColorBucket result;
|
||||||
if (nBucketsA > LONG_MAX - nBucketsB ||
|
if (nBucketsA > LONG_MAX - nBucketsB ||
|
||||||
(nBucketsA+nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) {
|
(nBucketsA+nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) {
|
||||||
|
@ -345,8 +345,8 @@ map_image_pixels(const Pixel *pixelData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int CUBE_LEVELS[8] = {4, 4, 4, 0, 2, 2, 2, 0};
|
const unsigned int CUBE_LEVELS[8] = {4, 4, 4, 0, 2, 2, 2, 0};
|
||||||
const int CUBE_LEVELS_ALPHA[8] = {3, 4, 3, 3, 2, 2, 2, 2};
|
const unsigned int CUBE_LEVELS_ALPHA[8] = {3, 4, 3, 3, 2, 2, 2, 2};
|
||||||
|
|
||||||
int quantize_octree(Pixel *pixelData,
|
int quantize_octree(Pixel *pixelData,
|
||||||
uint32_t nPixels,
|
uint32_t nPixels,
|
||||||
|
@ -365,8 +365,8 @@ int quantize_octree(Pixel *pixelData,
|
||||||
ColorBucket paletteBuckets = NULL;
|
ColorBucket paletteBuckets = NULL;
|
||||||
uint32_t *qp = NULL;
|
uint32_t *qp = NULL;
|
||||||
long i;
|
long i;
|
||||||
long nCoarseColors, nFineColors, nAlreadySubtracted;
|
unsigned long nCoarseColors, nFineColors, nAlreadySubtracted;
|
||||||
const int *cubeBits;
|
const unsigned int *cubeBits;
|
||||||
|
|
||||||
if (withAlpha) {
|
if (withAlpha) {
|
||||||
cubeBits = CUBE_LEVELS_ALPHA;
|
cubeBits = CUBE_LEVELS_ALPHA;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user