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 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

View File

@ -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;
}
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 {
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;
}
/* 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 = 0; i < j-1 ; i += 2)
hline8(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink);
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);
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;
}