From 8e8f63d4a5408daf8b907163822cad76210da725 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 5 Jan 2020 17:43:51 +1100 Subject: [PATCH 01/12] Only draw each polygon pixel once --- .../images/imagedraw_ellipse_translucent.png | Bin 0 -> 390 bytes Tests/test_imagedraw.py | 12 ++++ src/libImaging/Draw.c | 57 ++++++++++++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 Tests/images/imagedraw_ellipse_translucent.png diff --git a/Tests/images/imagedraw_ellipse_translucent.png b/Tests/images/imagedraw_ellipse_translucent.png new file mode 100644 index 0000000000000000000000000000000000000000..964dce678891635288ce01cdbcb7c168376e8ced GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^DImVJ6q_#(TV`nDgp%Od!VcWs}3 zD=6asi5K@Ovff{2&iC~+{Wo{{Su^dD%3x9HoUnBqKq~XvjY`gNo%?G}T6+)k=XYUD{&ufbfxA^zv!@k6WWfAsq4}HU+ ZfusNFtZZDoTc#z5@9FC2vd$@?2>_1esBi!P literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 4535a4838..ba5684dcd 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -234,6 +234,18 @@ class TestImageDraw(PillowTestCase): for mode in ["RGB", "L"]: self.helper_ellipse(mode, BBOX2) + def test_ellipse_translucent(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im, "RGBA") + + # Act + draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) + + # Assert + expected = Image.open("Tests/images/imagedraw_ellipse_translucent.png") + self.assert_image_similar(im, expected, 1) + def test_ellipse_edge(self): # Arrange im = Image.new("RGB", (W, H)) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index dee7c524d..828134b8e 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -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. */ @@ -442,10 +471,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, } 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) { - (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); continue; } 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++) { int j = 0; + int x_pos = 0; for (i = 0; i < edge_count; i++) { Edge* current = edge_table[i]; 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); 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); From 56f30ef79228eaa57d950e1b266b58356bcfbc14 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 5 Jan 2020 19:49:06 +1100 Subject: [PATCH 02/12] Allow explicit zero width to hide outline --- Tests/images/imagedraw_chord_zero_width.png | Bin 0 -> 427 bytes Tests/images/imagedraw_ellipse_zero_width.png | Bin 0 -> 339 bytes .../images/imagedraw_pieslice_zero_width.png | Bin 0 -> 403 bytes .../images/imagedraw_rectangle_zero_width.png | Bin 0 -> 212 bytes Tests/test_imagedraw.py | 48 ++++++++++++++++++ docs/reference/ImageDraw.rst | 8 +-- src/PIL/ImageDraw.py | 16 +++--- 7 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 Tests/images/imagedraw_chord_zero_width.png create mode 100644 Tests/images/imagedraw_ellipse_zero_width.png create mode 100644 Tests/images/imagedraw_pieslice_zero_width.png create mode 100644 Tests/images/imagedraw_rectangle_zero_width.png diff --git a/Tests/images/imagedraw_chord_zero_width.png b/Tests/images/imagedraw_chord_zero_width.png new file mode 100644 index 0000000000000000000000000000000000000000..c1c0058d766c76757ca24dc7503b64a66f288cdf GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^DImurTEP!%J(+eqMJtDqXcYE&qM{irn?P z>rbA3eeIiF)c!|7t!7!ouh~>5Ga>Br(%z{>SM2t3#U0i+ zd8yhnwfYd>-jIzV>DuqN9yhhQ`fbvYNpdSUgxBdDPFz)$TgL0Jy0@%XRCMp8StIa$mYDXGy==$0BG=E{=yW+d zXN|y@Su)z2s(JfGSMGgw+-r*D>Z|;VmLG{VjO6avs$dzmefEsq@@m>~E_~aqsePxgz5cP1-HL0ei(!Gs!0_)jbM^&Y TAML6$njk?>S3j3^P67koHc8A~jnENAQ za_ZHDZ_`STpIWu@LGS9@ZH+TGWvQ43|DDL38e0FvKD2mVu0%lCg57Jf&h1`hb=W7& zA+qt>rd7ske7CC3T+^_L=@gfS_5u_}-8&YLGRFFUtYCWkA*Q@dY&nM;*qxc=EV*v9 zG4^}1*)&K;RAelWlm7SYft{EKMcH9Mtb<9>Sk zMV&gc?aa}CC%5FTj4nDkdrc+#s|khax)UVo%1_P&M9T48SsI>W^M!b0`$?gBa`cg>%N zlh$d>Uvnnz)z(k*Tz=(-pPR4p{+v)<<4n^%tomyX2zu`m=Dyb!=pXUnn@8OHhJ|vgj9_WK64C@<$fL_jL7h JS?83{1OP8CH+292 literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 4535a4838..863832bb7 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -214,6 +214,18 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + def test_chord_zero_width(self): + # 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: + self.assert_image_equal(im, expected) + def helper_ellipse(self, mode, bbox): # Arrange im = Image.new(mode, (W, H)) @@ -290,6 +302,18 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + def test_ellipse_zero_width(self): + # 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: + self.assert_image_equal(im, expected) + def helper_line(self, points): # Arrange im = Image.new("RGB", (W, H)) @@ -392,6 +416,18 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_similar(im, Image.open(expected), 1) + def test_pieslice_zero_width(self): + # 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: + self.assert_image_equal(im, expected) + def helper_point(self, points): # Arrange im = Image.new("RGB", (W, H)) @@ -496,6 +532,18 @@ class TestImageDraw(PillowTestCase): # Assert self.assert_image_equal(im, Image.open(expected)) + def test_rectangle_zero_width(self): + # 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: + self.assert_image_equal(im, expected) + def test_rectangle_I16(self): # Arrange im = Image.new("I;16", (W, H)) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 51eaf925e..4a86a31f7 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -154,7 +154,7 @@ Methods To paste pixel data into an image, use the :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 with a straight line. @@ -168,7 +168,7 @@ Methods .. 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. @@ -198,7 +198,7 @@ Methods .. 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 center of the bounding box. @@ -236,7 +236,7 @@ Methods :param outline: Color to use for the outline. :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. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index c6e12150e..7abd459f9 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -134,20 +134,20 @@ class ImageDraw: if ink is not None: 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.""" ink, fill = self._getink(outline, fill) if fill is not None: 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) - def ellipse(self, xy, fill=None, outline=None, width=0): + def ellipse(self, xy, fill=None, outline=None, width=1): """Draw an ellipse.""" ink, fill = self._getink(outline, fill) if fill is not None: 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) def line(self, xy, fill=None, width=0, joint=None): @@ -219,12 +219,12 @@ class ImageDraw: if ink is not None and ink != fill: 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.""" ink, fill = self._getink(outline, fill) if fill is not None: 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) def point(self, xy, fill=None): @@ -241,12 +241,12 @@ class ImageDraw: if ink is not None and ink != fill: 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.""" ink, fill = self._getink(outline, fill) if fill is not None: 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) def _multiline_check(self, text): From c80b598b3423c5c37594db79b78a2e92db69213f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Jan 2020 19:29:55 +1100 Subject: [PATCH 03/12] Updated release schedule [ci skip] --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 9614b133f..da73f6417 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,7 +2,7 @@ ## Main Release -Released quarterly on the first day of January, April, July, October. +Released on January 2nd, April 1st, July 1st and October 15th. * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154 * [ ] Develop and prepare release in `master` branch. From b42a4fede973034fdc25f7c4f6804ef1c3612e9e Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 15 Jan 2020 06:18:32 +1100 Subject: [PATCH 04/12] Updated wording [ci skip] Co-Authored-By: Hugo van Kemenade --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index da73f6417..3a285662c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,7 +2,7 @@ ## Main Release -Released on January 2nd, April 1st, July 1st and October 15th. +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 * [ ] Develop and prepare release in `master` branch. From e544fd5fb725ac15d830f05119ad057dd8f9a519 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 13 Feb 2020 17:08:44 -0800 Subject: [PATCH 05/12] Simplify command discovery with stdlib shutil.which() Use the builtin shutil.which() instead of reimplementing. For the single use that used the output of the command, use subprocess.run(). --- Tests/helper.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index e4b1d917c..3b2341012 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -4,6 +4,7 @@ Helper functions. import logging import os +import shutil import subprocess import sys import tempfile @@ -191,9 +192,12 @@ class PillowTestCase(unittest.TestCase): raise OSError() outfile = self.tempfile("temp.png") - if command_succeeds([IMCONVERT, f, outfile]): - return Image.open(outfile) - raise OSError() + rc = subprocess.call( + [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ) + if rc: + raise OSError + return Image.open(outfile) @unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") @@ -268,34 +272,20 @@ def hopper(mode=None, cache={}): 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(): - return command_succeeds(["djpeg", "-version"]) + return bool(shutil.which("djpeg")) def cjpeg_available(): - return command_succeeds(["cjpeg", "-version"]) + return bool(shutil.which("cjpeg")) def netpbm_available(): - return command_succeeds(["ppmquant", "--version"]) and command_succeeds( - ["ppmtogif", "--version"] - ) + return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) def imagemagick_available(): - return IMCONVERT and command_succeeds([IMCONVERT, "-version"]) + return bool(IMCONVERT and shutil.which(IMCONVERT)) def on_appveyor(): From 03fc9f341ef2d5a00f1b4d31ca00b0c46c49f29d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2020 10:58:04 +1100 Subject: [PATCH 06/12] Added GitHub Actions links to badges [ci skip] --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 253dc89a5..f02268df5 100644 --- a/README.rst +++ b/README.rst @@ -61,15 +61,19 @@ To report a security vulnerability, please follow the procedure described in the :alt: AppVeyor CI build status (Windows) .. |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) .. |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) .. |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) .. |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) .. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg From 919abe2555442ee244f638d1f6522abb2aeb6dbf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2020 21:17:26 +1100 Subject: [PATCH 07/12] Updated test --- Tests/test_imagedraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index f76967242..5e0f537e2 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -244,7 +244,7 @@ class TestImageDraw(PillowTestCase): # Assert expected = Image.open("Tests/images/imagedraw_ellipse_translucent.png") - self.assert_image_similar(im, expected, 1) + assert_image_similar(im, expected, 1) def test_ellipse_edge(self): # Arrange From e817ed0d3eb838e63836dd5f976a007a9775ce89 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 3 Nov 2019 12:51:40 -0800 Subject: [PATCH 08/12] Correct str/bytes mixup in ContainerIO Image data is expected to be read in bytes mode, not text mode so ContainerIO should return bytes in all methods. The passed in file handler is expected to be opened in bytes mode (as TarIO already does). --- Tests/test_file_container.py | 40 ++++++++++++++++++------------------ src/PIL/ContainerIO.py | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 91166b39e..d494e1088 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -20,7 +20,7 @@ def test_isatty(): def test_seek_mode_0(): # Arrange mode = 0 - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -34,7 +34,7 @@ def test_seek_mode_0(): def test_seek_mode_1(): # Arrange mode = 1 - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -48,7 +48,7 @@ def test_seek_mode_1(): def test_seek_mode_2(): # Arrange mode = 2 - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -61,7 +61,7 @@ def test_seek_mode_2(): def test_read_n0(): # Arrange - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -69,12 +69,12 @@ def test_read_n0(): data = container.read() # Assert - assert data == "7\nThis is line 8\n" + assert data == b"7\nThis is line 8\n" def test_read_n(): # Arrange - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -82,12 +82,12 @@ def test_read_n(): data = container.read(3) # Assert - assert data == "7\nT" + assert data == b"7\nT" def test_read_eof(): # Arrange - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -95,34 +95,34 @@ def test_read_eof(): data = container.read() # Assert - assert data == "" + assert data == b"" def test_readline(): # Arrange - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 0, 120) # Act data = container.readline() # Assert - assert data == "This is line 1\n" + assert data == b"This is line 1\n" def test_readlines(): # Arrange expected = [ - "This is line 1\n", - "This is line 2\n", - "This is line 3\n", - "This is line 4\n", - "This is line 5\n", - "This is line 6\n", - "This is line 7\n", - "This is line 8\n", + b"This is line 1\n", + b"This is line 2\n", + b"This is line 3\n", + b"This is line 4\n", + b"This is line 5\n", + b"This is line 6\n", + b"This is line 7\n", + b"This is line 8\n", ] - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 0, 120) # Act diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 9727601ab..8e9041210 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -82,7 +82,7 @@ class ContainerIO: else: n = self.length - self.pos if not n: # EOF - return "" + return b"" self.pos = self.pos + n return self.fh.read(n) @@ -92,13 +92,13 @@ class ContainerIO: :returns: An 8-bit string. """ - s = "" + s = b"" while True: c = self.read(1) if not c: break s = s + c - if c == "\n": + if c == b"\n": break return s From f958e2f8ed7b12582836a6b6c83468c51230182a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Jan 2020 21:06:02 +1100 Subject: [PATCH 09/12] Return strings or bytes from ContainerIO according to the file object mode --- Tests/test_file_container.py | 102 ++++++++++++++++++++--------------- src/PIL/ContainerIO.py | 6 +-- 2 files changed, 61 insertions(+), 47 deletions(-) diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index d494e1088..b752e217f 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -61,73 +61,87 @@ def test_seek_mode_2(): def test_read_n0(): # Arrange - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 22, 100) - # Act - container.seek(81) - data = container.read() + # Act + container.seek(81) + data = container.read() - # Assert - assert data == b"7\nThis is line 8\n" + # Assert + if bytesmode: + data = data.decode() + assert data == "7\nThis is line 8\n" def test_read_n(): # Arrange - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 22, 100) - # Act - container.seek(81) - data = container.read(3) + # Act + container.seek(81) + data = container.read(3) - # Assert - assert data == b"7\nT" + # Assert + if bytesmode: + data = data.decode() + assert data == "7\nT" def test_read_eof(): # Arrange - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 22, 100) - # Act - container.seek(100) - data = container.read() + # Act + container.seek(100) + data = container.read() - # Assert - assert data == b"" + # Assert + if bytesmode: + data = data.decode() + assert data == "" def test_readline(): # Arrange - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 0, 120) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) - # Act - data = container.readline() + # Act + data = container.readline() - # Assert - assert data == b"This is line 1\n" + # Assert + if bytesmode: + data = data.decode() + assert data == "This is line 1\n" def test_readlines(): # Arrange - expected = [ - b"This is line 1\n", - b"This is line 2\n", - b"This is line 3\n", - b"This is line 4\n", - b"This is line 5\n", - b"This is line 6\n", - b"This is line 7\n", - b"This is line 8\n", - ] - with open(TEST_FILE, "rb") as fh: - container = ContainerIO.ContainerIO(fh, 0, 120) + for bytesmode in (True, False): + expected = [ + "This is line 1\n", + "This is line 2\n", + "This is line 3\n", + "This is line 4\n", + "This is line 5\n", + "This is line 6\n", + "This is line 7\n", + "This is line 8\n", + ] + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) - # Act - data = container.readlines() + # Act + data = container.readlines() - # Assert - - assert data == expected + # Assert + if bytesmode: + data = [line.decode() for line in data] + assert data == expected diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 8e9041210..48c0081fc 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -82,7 +82,7 @@ class ContainerIO: else: n = self.length - self.pos if not n: # EOF - return b"" + return b"" if "b" in self.fh.mode else "" self.pos = self.pos + n return self.fh.read(n) @@ -92,13 +92,13 @@ class ContainerIO: :returns: An 8-bit string. """ - s = b"" + s = b"" if "b" in self.fh.mode else "" while True: c = self.read(1) if not c: break s = s + c - if c == b"\n": + if c == (b"\n" if "b" in self.fh.mode else "\n"): break return s From 8acf77a0420fbf244684d5735a30747123a3b543 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2020 20:53:02 +1100 Subject: [PATCH 10/12] For effiency, set newline character outside of loop --- src/PIL/ContainerIO.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 48c0081fc..5bb0086f6 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -93,12 +93,13 @@ class ContainerIO: :returns: An 8-bit string. """ s = b"" if "b" in self.fh.mode else "" + newline_character = b"\n" if "b" in self.fh.mode else "\n" while True: c = self.read(1) if not c: break s = s + c - if c == (b"\n" if "b" in self.fh.mode else "\n"): + if c == newline_character: break return s From 74351dc692631520f1de6c00ee1baacad39f033d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Dec 2019 19:17:57 +1100 Subject: [PATCH 11/12] Fixed sign comparison warnings --- src/libImaging/QuantHeap.c | 10 +++++----- src/libImaging/QuantOctree.c | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libImaging/QuantHeap.c b/src/libImaging/QuantHeap.c index 121b87275..498d44b1d 100644 --- a/src/libImaging/QuantHeap.c +++ b/src/libImaging/QuantHeap.c @@ -26,8 +26,8 @@ struct _Heap { void **heap; - int heapsize; - int heapcount; + unsigned int heapsize; + unsigned int heapcount; HeapCmpFunc cf; }; @@ -44,7 +44,7 @@ void ImagingQuantHeapFree(Heap *h) { free(h); } -static int _heap_grow(Heap *h,int newsize) { +static int _heap_grow(Heap *h,unsigned int newsize) { void *newheap; if (!newsize) newsize=h->heapsize<<1; if (newsizeheapsize) return 0; @@ -64,7 +64,7 @@ static int _heap_grow(Heap *h,int newsize) { #ifdef DEBUG static int _heap_test(Heap *h) { - int k; + unsigned int k; for (k=1;k*2<=h->heapcount;k++) { if (h->cf(h,h->heap[k],h->heap[k*2])<0) { printf ("heap is bad\n"); @@ -80,7 +80,7 @@ static int _heap_test(Heap *h) { #endif int ImagingQuantHeapRemove(Heap* h,void **r) { - int k,l; + unsigned int k,l; void *v; if (!h->heapcount) { diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index 6c0f605c9..83d987544 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -44,7 +44,7 @@ typedef struct _ColorCube{ unsigned int rWidth, gWidth, bWidth, aWidth; unsigned int rOffset, gOffset, bOffset, aOffset; - long size; + unsigned long size; ColorBucket buckets; } *ColorCube; @@ -134,10 +134,10 @@ add_color_to_color_cube(const ColorCube cube, const Pixel *p) { bucket->a += p->c.a; } -static long +static unsigned long count_used_color_buckets(const ColorCube cube) { - long usedBuckets = 0; - long i; + unsigned long usedBuckets = 0; + unsigned long i; for (i=0; i < cube->size; i++) { if (cube->buckets[i].count > 0) { usedBuckets += 1; @@ -194,7 +194,7 @@ void add_bucket_values(ColorBucket src, ColorBucket dst) { /* expand or shrink a given cube to level */ 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; long src_pos, dst_pos; @@ -302,7 +302,7 @@ void add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long } 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; if (nBucketsA > LONG_MAX - nBucketsB || (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 int CUBE_LEVELS_ALPHA[8] = {3, 4, 3, 3, 2, 2, 2, 2}; +const unsigned int CUBE_LEVELS[8] = {4, 4, 4, 0, 2, 2, 2, 0}; +const unsigned int CUBE_LEVELS_ALPHA[8] = {3, 4, 3, 3, 2, 2, 2, 2}; int quantize_octree(Pixel *pixelData, uint32_t nPixels, @@ -365,8 +365,8 @@ int quantize_octree(Pixel *pixelData, ColorBucket paletteBuckets = NULL; uint32_t *qp = NULL; long i; - long nCoarseColors, nFineColors, nAlreadySubtracted; - const int *cubeBits; + unsigned long nCoarseColors, nFineColors, nAlreadySubtracted; + const unsigned int *cubeBits; if (withAlpha) { cubeBits = CUBE_LEVELS_ALPHA; From 07b9f890561384a8c814b15eef6dc131b9f2b25c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2020 22:47:59 +1100 Subject: [PATCH 12/12] Updated CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8b48ac1a8..c17b5fdb3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 7.1.0 (unreleased) ------------------ +- Only draw each polygon pixel once #4333 + [radarhere] + - Add support for shooting situation Exif IFD tags #4398 [alexagv]