Merge pull request #737 from wiredfool/terseus_imagedraw

Merged Imagedraw rewrite
This commit is contained in:
Alex Clark ☺ 2014-07-01 14:15:11 -04:00
commit b2ed31e8cd
20 changed files with 234 additions and 161 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 293 B

View File

@ -3,6 +3,13 @@ from helper import unittest, PillowTestCase, tearDownModule, lena
from PIL import Image from PIL import Image
from PIL import ImageColor from PIL import ImageColor
from PIL import ImageDraw 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 import sys
@ -249,7 +256,129 @@ class TestImageDraw(PillowTestCase):
im, Image.open("Tests/images/imagedraw_floodfill2.png")) 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__': if __name__ == '__main__':
unittest.main() unittest.main()
# End of file # End of file

View File

@ -49,6 +49,13 @@
#define BLEND(mask, in1, in2, tmp1, tmp2)\ #define BLEND(mask, in1, in2, tmp1, tmp2)\
(MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, 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 */ /* Primitives */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -61,6 +68,9 @@ typedef struct {
float dx; float dx;
} Edge; } Edge;
/* Type used in "polygon*" functions */
typedef void (*hline_handler)(Imaging, int, int, int, int);
static inline void static inline void
point8(Imaging im, int x, int y, int ink) point8(Imaging im, int x, int y, int ink)
{ {
@ -403,177 +413,100 @@ x_cmp(const void *x0, const void *x1)
return 0; return 0;
} }
/*
* Filled polygon draw function using scan line algorithm.
*/
static inline int 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; 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) /* Initialize the edge table and find polygon boundaries */
ymin = 0; edge_table = malloc(sizeof(Edge*) * n);
if (ymax >= im->ysize) if (!edge_table) {
ymax = im->ysize-1;
/* Process polygon edges */
xx = malloc(n * sizeof(float));
if (!xx)
return -1; 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;
} }
if (j == 2) {
if (xx[0] < xx[1]) for (i = 0; i < n; i++) {
hline8(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); /* This causes that the pixels of horizontal edges are drawn twice :(
else * but without it there are inconsistencies in ellipses */
hline8(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); if (e[i].ymin == e[i].ymax) {
} else { (*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;
}
/* Needed to draw consistent polygons */
if (ymin == current->ymax && ymin < ymax) {
xx[j] = xx[j - 1];
j++;
}
}
qsort(xx, j, sizeof(float), x_cmp); qsort(xx, j, sizeof(float), x_cmp);
for (i = 0; i < j-1 ; i += 2) for (i = 1; i < j; i += 2) {
hline8(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink);
} }
} }
free(xx); free(xx);
free(edge_table);
return 0; 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 static inline int
polygon32(Imaging im, int n, Edge *e, int ink, int eofill) polygon32(Imaging im, int n, Edge *e, int ink, int eofill)
{ {
int i, j; return polygon_generic(im, n, e, ink, eofill, hline32);
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;
} }
static inline int static inline int
polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill)
{ {
int i, j; return polygon_generic(im, n, e, ink, eofill, hline32rgba);
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;
} }
static inline void static inline void
@ -663,11 +596,11 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1,
{ {
DRAW* draw; DRAW* draw;
INT32 ink; INT32 ink;
Edge e[4];
int dx, dy; 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(); DRAWINIT();
@ -678,24 +611,35 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1,
dx = x1-x0; dx = x1-x0;
dy = y1-y0; dy = y1-y0;
if (dx == 0 && dy == 0) { if (dx == 0 && dy == 0) {
draw->point(im, x0, y0, ink); draw->point(im, x0, y0, ink);
return 0; 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); dxmin = ROUND_DOWN(ratio_min * dy);
dy = (int) floor(d * (x1-x0) + 0.5); 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+0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]);
add_edge(e+1, x1 - dx, y1 + dy, x1 + dx, y1 - dy); add_edge(e+1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]);
add_edge(e+2, x1 + dx, y1 - dy, x0 + dx, y0 - dy); add_edge(e+2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]);
add_edge(e+3, x0 + dx, y0 - dy, x0 - dx, y0 + dy); add_edge(e+3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]);
draw->polygon(im, 4, e, ink, 0); draw->polygon(im, 4, e, ink, 0);
}
return 0; return 0;
} }