Repository: python-pillow/Pillow

Issue:
Add width argument to rectangle
PIL's rectangle doesn't support the `width` argument. so if we want to change the width of line, we have to use `ImageDraw.Draw(im).line` four times. So I suggest the PIL's rectangle will support the `width` argument.


Discussion:
The line width would be nice to be able to specify on all basic shapes - for example ellipses as well.

Resolving patch:
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -118,11 +118,11 @@ def _getink(self, ink, fill=None):
                 fill = self.draw.draw_ink(fill, self.mode)
         return ink, fill
 
-    def arc(self, xy, start, end, fill=None):
+    def arc(self, xy, start, end, fill=None, width=0):
         """Draw an arc."""
         ink, fill = self._getink(fill)
         if ink is not None:
-            self.draw.draw_arc(xy, start, end, ink)
+            self.draw.draw_arc(xy, start, end, ink, width)
 
     def bitmap(self, xy, bitmap, fill=None):
         """Draw a bitmap."""
@@ -133,21 +133,21 @@ def bitmap(self, xy, bitmap, fill=None):
         if ink is not None:
             self.draw.draw_bitmap(xy, bitmap.im, ink)
 
-    def chord(self, xy, start, end, fill=None, outline=None):
+    def chord(self, xy, start, end, fill=None, outline=None, width=0):
         """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:
-            self.draw.draw_chord(xy, start, end, ink, 0)
+            self.draw.draw_chord(xy, start, end, ink, 0, width)
 
-    def ellipse(self, xy, fill=None, outline=None):
+    def ellipse(self, xy, fill=None, outline=None, width=0):
         """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:
-            self.draw.draw_ellipse(xy, ink, 0)
+            self.draw.draw_ellipse(xy, ink, 0, width)
 
     def line(self, xy, fill=None, width=0):
         """Draw a line, or a connected sequence of line segments."""
@@ -164,13 +164,13 @@ def shape(self, shape, fill=None, outline=None):
         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):
+    def pieslice(self, xy, start, end, fill=None, outline=None, width=0):
         """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:
-            self.draw.draw_pieslice(xy, start, end, ink, 0)
+            self.draw.draw_pieslice(xy, start, end, ink, 0, width)
 
     def point(self, xy, fill=None):
         """Draw one or more individual pixels."""
@@ -186,13 +186,13 @@ def polygon(self, xy, fill=None, outline=None):
         if ink is not None and ink != fill:
             self.draw.draw_polygon(xy, ink, 0)
 
-    def rectangle(self, xy, fill=None, outline=None):
+    def rectangle(self, xy, fill=None, outline=None, width=0):
         """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:
-            self.draw.draw_rectangle(xy, ink, 0)
+            self.draw.draw_rectangle(xy, ink, 0, width)
 
     def _multiline_check(self, text):
         """Draw text."""


Test patch:
diff --git a/Tests/images/imagedraw_arc_width.png b/Tests/images/imagedraw_arc_width.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_arc_width.png differ
diff --git a/Tests/images/imagedraw_arc_width_fill.png b/Tests/images/imagedraw_arc_width_fill.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_arc_width_fill.png differ
diff --git a/Tests/images/imagedraw_chord_width.png b/Tests/images/imagedraw_chord_width.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_chord_width.png differ
diff --git a/Tests/images/imagedraw_chord_width_fill.png b/Tests/images/imagedraw_chord_width_fill.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_chord_width_fill.png differ
diff --git a/Tests/images/imagedraw_ellipse_width.png b/Tests/images/imagedraw_ellipse_width.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width.png differ
diff --git a/Tests/images/imagedraw_ellipse_width_fill.png b/Tests/images/imagedraw_ellipse_width_fill.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width_fill.png differ
diff --git a/Tests/images/imagedraw_pieslice_width.png b/Tests/images/imagedraw_pieslice_width.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_pieslice_width.png differ
diff --git a/Tests/images/imagedraw_pieslice_width_fill.png b/Tests/images/imagedraw_pieslice_width_fill.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_pieslice_width_fill.png differ
diff --git a/Tests/images/imagedraw_rectangle_width.png b/Tests/images/imagedraw_rectangle_width.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_rectangle_width.png differ
diff --git a/Tests/images/imagedraw_rectangle_width_fill.png b/Tests/images/imagedraw_rectangle_width_fill.png
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_rectangle_width_fill.png differ
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -102,6 +102,30 @@ def test_arc_no_loops(self):
         self.assert_image_similar(
             im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1)
 
+    def test_arc_width(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_arc_width.png"
+
+        # Act
+        draw.arc(BBOX1, 10, 260, width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
+    def test_arc_width_fill(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_arc_width_fill.png"
+
+        # Act
+        draw.arc(BBOX1, 10, 260, fill="yellow", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
     def test_bitmap(self):
         # Arrange
         small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
@@ -137,6 +161,30 @@ def test_chord2(self):
             self.helper_chord(mode, BBOX2, 0, 180)
             self.helper_chord(mode, BBOX2, 0.5, 180.4)
 
+    def test_chord_width(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_chord_width.png"
+
+        # Act
+        draw.chord(BBOX1, 10, 260, outline="yellow", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
+    def test_chord_width_fill(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_chord_width_fill.png"
+
+        # Act
+        draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
     def helper_ellipse(self, mode, bbox):
         # Arrange
         im = Image.new(mode, (W, H))
@@ -179,6 +227,30 @@ def test_ellipse_symmetric(self):
             draw.ellipse(bbox, fill="green", outline="blue")
             self.assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT))
 
+    def test_ellipse_width(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_ellipse_width.png"
+
+        # Act
+        draw.ellipse(BBOX1, outline="blue", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
+    def test_ellipse_width_fill(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_ellipse_width_fill.png"
+
+        # Act
+        draw.ellipse(BBOX1, fill="green", outline="blue", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
     def helper_line(self, points):
         # Arrange
         im = Image.new("RGB", (W, H))
@@ -259,6 +331,30 @@ def test_pieslice2(self):
         self.helper_pieslice(BBOX2, -90, 45)
         self.helper_pieslice(BBOX2, -90.5, 45.4)
 
+    def test_pieslice_width(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_pieslice_width.png"
+
+        # Act
+        draw.pieslice(BBOX1, 10, 260, outline="blue", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
+    def test_pieslice_width_fill(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_pieslice_width_fill.png"
+
+        # Act
+        draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5)
+
+        # Assert
+        self.assert_image_similar(im, Image.open(expected), 1)
+
     def helper_point(self, points):
         # Arrange
         im = Image.new("RGB", (W, H))
@@ -343,6 +439,30 @@ def test_big_rectangle(self):
         # Assert
         self.assert_image_similar(im, Image.open(expected), 1)
 
+    def test_rectangle_width(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_rectangle_width.png"
+
+        # Act
+        draw.rectangle(BBOX1, outline="green", width=5)
+
+        # Assert
+        self.assert_image_equal(im, Image.open(expected))
+
+    def test_rectangle_width_fill(self):
+        # Arrange
+        im = Image.new("RGB", (W, H))
+        draw = ImageDraw.Draw(im)
+        expected = "Tests/images/imagedraw_rectangle_width_fill.png"
+
+        # Act
+        draw.rectangle(BBOX1, fill="blue", outline="green", width=5)
+
+        # Assert
+        self.assert_image_equal(im, Image.open(expected))
+
     def test_floodfill(self):
         # Arrange
         im = Image.new("RGB", (W, H))