diff --git a/Tests/images/imagedraw/line_horizontal_slope1px_w2px.png b/Tests/images/imagedraw/line_horizontal_slope1px_w2px.png new file mode 100644 index 000000000..aaed76786 Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_slope1px_w2px.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w101px.png b/Tests/images/imagedraw/line_horizontal_w101px.png new file mode 100644 index 000000000..bbcc8133f Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w101px.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w2px_inverted.png b/Tests/images/imagedraw/line_horizontal_w2px_inverted.png new file mode 100644 index 000000000..a2bb3bbab Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w2px_inverted.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w2px_normal.png b/Tests/images/imagedraw/line_horizontal_w2px_normal.png new file mode 100644 index 000000000..a6d409ea4 Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w2px_normal.png differ diff --git a/Tests/images/imagedraw/line_horizontal_w3px.png b/Tests/images/imagedraw/line_horizontal_w3px.png new file mode 100644 index 000000000..06334d666 Binary files /dev/null and b/Tests/images/imagedraw/line_horizontal_w3px.png differ diff --git a/Tests/images/imagedraw/line_oblique_45_w3px_a.png b/Tests/images/imagedraw/line_oblique_45_w3px_a.png new file mode 100644 index 000000000..55c535e29 Binary files /dev/null and b/Tests/images/imagedraw/line_oblique_45_w3px_a.png differ diff --git a/Tests/images/imagedraw/line_oblique_45_w3px_b.png b/Tests/images/imagedraw/line_oblique_45_w3px_b.png new file mode 100644 index 000000000..8c1036559 Binary files /dev/null and b/Tests/images/imagedraw/line_oblique_45_w3px_b.png differ diff --git a/Tests/images/imagedraw/line_vertical_slope1px_w2px.png b/Tests/images/imagedraw/line_vertical_slope1px_w2px.png new file mode 100644 index 000000000..1d48a57e8 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_slope1px_w2px.png differ diff --git a/Tests/images/imagedraw/line_vertical_w101px.png b/Tests/images/imagedraw/line_vertical_w101px.png new file mode 100644 index 000000000..558450950 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w101px.png differ diff --git a/Tests/images/imagedraw/line_vertical_w2px_inverted.png b/Tests/images/imagedraw/line_vertical_w2px_inverted.png new file mode 100644 index 000000000..74d666b88 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w2px_inverted.png differ diff --git a/Tests/images/imagedraw/line_vertical_w2px_normal.png b/Tests/images/imagedraw/line_vertical_w2px_normal.png new file mode 100644 index 000000000..5b18a7c94 Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w2px_normal.png differ diff --git a/Tests/images/imagedraw/line_vertical_w3px.png b/Tests/images/imagedraw/line_vertical_w3px.png new file mode 100644 index 000000000..4e5234f2a Binary files /dev/null and b/Tests/images/imagedraw/line_vertical_w3px.png differ diff --git a/Tests/images/imagedraw/square.png b/Tests/images/imagedraw/square.png new file mode 100644 index 000000000..842ee4722 Binary files /dev/null and b/Tests/images/imagedraw/square.png differ diff --git a/Tests/images/imagedraw/triangle_right.png b/Tests/images/imagedraw/triangle_right.png new file mode 100644 index 000000000..e91fa5802 Binary files /dev/null and b/Tests/images/imagedraw/triangle_right.png differ diff --git a/Tests/images/imagedraw_ellipse.png b/Tests/images/imagedraw_ellipse.png index fb03fd148..b52b12802 100644 Binary files a/Tests/images/imagedraw_ellipse.png and b/Tests/images/imagedraw_ellipse.png differ diff --git a/Tests/images/imagedraw_line.png b/Tests/images/imagedraw_line.png index 6d0e6994d..f7303a832 100644 Binary files a/Tests/images/imagedraw_line.png and b/Tests/images/imagedraw_line.png differ diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png index 1b2acff92..2f8c09191 100644 Binary files a/Tests/images/imagedraw_pieslice.png and b/Tests/images/imagedraw_pieslice.png differ diff --git a/Tests/images/imagedraw_polygon.png b/Tests/images/imagedraw_polygon.png index 5f160be76..7199a25dd 100644 Binary files a/Tests/images/imagedraw_polygon.png and b/Tests/images/imagedraw_polygon.png differ diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 996c45e79..4610e2b0b 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -3,6 +3,13 @@ from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageColor from PIL import ImageDraw +import os.path + +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +GRAY = (190, 190, 190) +DEFAULT_MODE = 'RGB' +IMAGES_PATH = os.path.join('Tests', 'images', 'imagedraw') import sys @@ -249,7 +256,129 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_floodfill2.png")) + def create_base_image_draw(self, size, + mode=DEFAULT_MODE, + background1=WHITE, + background2=GRAY): + img = Image.new(mode, size, background1) + for x in range(0, size[0]): + for y in range(0, size[1]): + if (x + y) % 2 == 0: + img.putpixel((x, y), background2) + return (img, ImageDraw.Draw(img)) + + + def test_square(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) + expected.load() + img, draw = self.create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + self.assert_image_equal(img, expected, 'square as normal polygon failed') + img, draw = self.create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + self.assert_image_equal(img, expected, 'square as inverted polygon failed') + img, draw = self.create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + self.assert_image_equal(img, expected, 'square as normal rectangle failed') + img, draw = self.create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + self.assert_image_equal(img, expected, 'square as inverted rectangle failed') + + + def test_triangle_right(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'triangle_right.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + self.assert_image_equal(img, expected, 'triangle right failed') + + + def test_line_horizontal(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_normal.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth horizontal normal 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_inverted.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth horizontal inverted 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w3px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth horizontal normal 3px wide failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth horizontal inverted 3px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w101px.png')) + expected.load() + img, draw = self.create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + self.assert_image_equal(img, expected, 'line straigth horizontal 101px wide failed') + + def test_line_h_s1_w2(self): + self.skipTest('failing') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_slope1px_w2px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + self.assert_image_equal(img, expected, 'line horizontal 1px slope 2px wide failed') + + + def test_line_vertical(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_normal.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth vertical normal 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_inverted.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 2) + self.assert_image_equal(img, expected, 'line straigth vertical inverted 2px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w3px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth vertical normal 3px wide failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line straigth vertical inverted 3px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w101px.png')) + expected.load() + img, draw = self.create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + self.assert_image_equal(img, expected, 'line straigth vertical 101px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_slope1px_w2px.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + self.assert_image_equal(img, expected, 'line vertical 1px slope 2px wide failed') + + + def test_line_oblique_45(self): + expected = Image.open(os.path.join(IMAGES_PATH, 'line_oblique_45_w3px_a.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 14), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 normal 3px wide A failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 14, 5, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide A failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_oblique_45_w3px_b.png')) + expected.load() + img, draw = self.create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 normal 3px wide B failed') + img, draw = self.create_base_image_draw((20, 20)) + draw.line((5, 14, 14, 5), BLACK, 3) + self.assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide B failed') + + if __name__ == '__main__': unittest.main() # End of file + diff --git a/libImaging/Draw.c b/libImaging/Draw.c index f13ba4df0..2ff03e049 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -49,6 +49,13 @@ #define BLEND(mask, in1, in2, tmp1, tmp2)\ (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2)) +/* + * Rounds around zero (up=away from zero, down=torwards zero) + * This guarantees that ROUND_UP|DOWN(f) == -ROUND_UP|DOWN(-f) + */ +#define ROUND_UP(f) ((int) ((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F))) +#define ROUND_DOWN(f) ((int) ((f) >= 0.0 ? ceil((f) - 0.5F) : -ceil(fabs(f) - 0.5F))) + /* -------------------------------------------------------------------- */ /* Primitives */ /* -------------------------------------------------------------------- */ @@ -61,6 +68,9 @@ typedef struct { float dx; } Edge; +/* Type used in "polygon*" functions */ +typedef void (*hline_handler)(Imaging, int, int, int, int); + static inline void point8(Imaging im, int x, int y, int ink) { @@ -403,177 +413,100 @@ x_cmp(const void *x0, const void *x1) return 0; } + +/* + * Filled polygon draw function using scan line algorithm. + */ static inline int -polygon8(Imaging im, int n, Edge *e, int ink, int eofill) +polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, + hline_handler hline) { - int i, j; - float *xx; - int ymin, ymax; - float y; - if (n <= 0) + Edge** edge_table; + float* xx; + int edge_count = 0; + int ymin = im->ysize - 1; + int ymax = 0; + int i; + + if (n <= 0) { return 0; - - /* Find upper and lower polygon boundary (within image) */ - - ymin = e[0].ymin; - ymax = e[0].ymax; - for (i = 1; i < n; i++) { - if (e[i].ymin < ymin) ymin = e[i].ymin; - if (e[i].ymax > ymax) ymax = e[i].ymax; } - if (ymin < 0) - ymin = 0; - if (ymax >= im->ysize) - ymax = im->ysize-1; - - /* Process polygon edges */ - - xx = malloc(n * sizeof(float)); - if (!xx) + /* Initialize the edge table and find polygon boundaries */ + edge_table = malloc(sizeof(Edge*) * n); + if (!edge_table) { return -1; + } - for (;ymin <= ymax; ymin++) { - y = ymin+0.5F; - for (i = j = 0; i < n; i++) - if (y >= e[i].ymin && y <= e[i].ymax) { - if (e[i].d == 0) - hline8(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; + for (i = 0; i < n; i++) { + /* This causes that the pixels of horizontal edges are 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) { + ymin = e[i].ymin; + } + if (ymax < e[i].ymax) { + ymax = e[i].ymax; + } + edge_table[edge_count++] = (e + i); + } + if (ymin < 0) { + ymin = 0; + } + if (ymax >= im->ysize) { + ymax = im->ysize - 1; + } + + /* Process the edge table with a scan line searching for intersections */ + xx = malloc(sizeof(float) * edge_count * 2); + if (!xx) { + free(edge_table); + return -1; + } + for (; ymin <= ymax; ymin++) { + int j = 0; + for (i = 0; i < edge_count; i++) { + Edge* current = edge_table[i]; + if (ymin >= current->ymin && ymin <= current->ymax) { + xx[j++] = (ymin - current->y0) * current->dx + current->x0; } - if (j == 2) { - if (xx[0] < xx[1]) - hline8(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - hline8(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); - } else { - qsort(xx, j, sizeof(float), x_cmp); - for (i = 0; i < j-1 ; i += 2) - hline8(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); + /* Needed to draw consistent polygons */ + if (ymin == current->ymax && ymin < ymax) { + xx[j] = xx[j - 1]; + j++; + } + } + 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); } } free(xx); - + free(edge_table); return 0; } +static inline int +polygon8(Imaging im, int n, Edge *e, int ink, int eofill) +{ + return polygon_generic(im, n, e, ink, eofill, hline8); +} + static inline int polygon32(Imaging im, int n, Edge *e, int ink, int eofill) { - int i, j; - float *xx; - int ymin, ymax; - float y; - - if (n <= 0) - return 0; - - /* Find upper and lower polygon boundary (within image) */ - - ymin = e[0].ymin; - ymax = e[0].ymax; - for (i = 1; i < n; i++) { - if (e[i].ymin < ymin) ymin = e[i].ymin; - if (e[i].ymax > ymax) ymax = e[i].ymax; - } - - if (ymin < 0) - ymin = 0; - if (ymax >= im->ysize) - ymax = im->ysize-1; - - /* Process polygon edges */ - - xx = malloc(n * sizeof(float)); - if (!xx) - return -1; - - for (;ymin <= ymax; ymin++) { - y = ymin+0.5F; - for (i = j = 0; i < n; i++) { - if (y >= e[i].ymin && y <= e[i].ymax) { - if (e[i].d == 0) - hline32(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; - } - } - if (j == 2) { - if (xx[0] < xx[1]) - hline32(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - hline32(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); - } else { - qsort(xx, j, sizeof(float), x_cmp); - for (i = 0; i < j-1 ; i += 2) - hline32(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); - } - } - - free(xx); - - return 0; + return polygon_generic(im, n, e, ink, eofill, hline32); } static inline int polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) { - int i, j; - float *xx; - int ymin, ymax; - float y; - - if (n <= 0) - return 0; - - /* Find upper and lower polygon boundary (within image) */ - - ymin = e[0].ymin; - ymax = e[0].ymax; - for (i = 1; i < n; i++) { - if (e[i].ymin < ymin) ymin = e[i].ymin; - if (e[i].ymax > ymax) ymax = e[i].ymax; - } - - if (ymin < 0) - ymin = 0; - if (ymax >= im->ysize) - ymax = im->ysize-1; - - /* Process polygon edges */ - - xx = malloc(n * sizeof(float)); - if (!xx) - return -1; - - for (;ymin <= ymax; ymin++) { - y = ymin+0.5F; - for (i = j = 0; i < n; i++) { - if (y >= e[i].ymin && y <= e[i].ymax) { - if (e[i].d == 0) - hline32rgba(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; - } - } - if (j == 2) { - if (xx[0] < xx[1]) - hline32rgba(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - hline32rgba(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); - } else { - qsort(xx, j, sizeof(float), x_cmp); - for (i = 0; i < j-1 ; i += 2) - hline32rgba(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); - } - } - - free(xx); - - return 0; + return polygon_generic(im, n, e, ink, eofill, hline32rgba); } static inline void @@ -663,11 +596,11 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, { DRAW* draw; INT32 ink; - - Edge e[4]; - int dx, dy; - double d; + double big_hypotenuse, small_hypotenuse, ratio_max, ratio_min; + int dxmin, dxmax, dymin, dymax; + Edge e[4]; + int vertices[4][2]; DRAWINIT(); @@ -678,24 +611,35 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, dx = x1-x0; dy = y1-y0; - if (dx == 0 && dy == 0) { draw->point(im, x0, y0, ink); return 0; } - d = width / sqrt((float) (dx*dx + dy*dy)) / 2.0; + big_hypotenuse = sqrt((double) (dx*dx + dy*dy)); + small_hypotenuse = (width - 1) / 2.0; + ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse; + ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse; - dx = (int) floor(d * (y1-y0) + 0.5); - dy = (int) floor(d * (x1-x0) + 0.5); + dxmin = ROUND_DOWN(ratio_min * dy); + dxmax = ROUND_DOWN(ratio_max * dy); + dymin = ROUND_DOWN(ratio_min * dx); + dymax = ROUND_DOWN(ratio_max * dx); + { + int vertices[4][2] = { + {x0 - dxmin, y0 + dymax}, + {x1 - dxmin, y1 + dymax}, + {x1 + dxmax, y1 - dymin}, + {x0 + dxmax, y0 - dymin} + }; - add_edge(e+0, x0 - dx, y0 + dy, x1 - dx, y1 + dy); - add_edge(e+1, x1 - dx, y1 + dy, x1 + dx, y1 - dy); - add_edge(e+2, x1 + dx, y1 - dy, x0 + dx, y0 - dy); - add_edge(e+3, x0 + dx, y0 - dy, x0 - dx, y0 + dy); - - draw->polygon(im, 4, e, ink, 0); + add_edge(e+0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]); + add_edge(e+1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]); + add_edge(e+2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]); + add_edge(e+3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]); + draw->polygon(im, 4, e, ink, 0); + } return 0; }