From deecd8a137c1481504fa48bcf41566e51eb5100a Mon Sep 17 00:00:00 2001 From: Terseus Date: Tue, 1 Apr 2014 13:01:03 +0200 Subject: [PATCH 001/488] Refactored polygon functions to unify logic The functions `polygon8`, `polygon32` and `polygon32rgba` all have exactly the same logic in code, only changes the hline function called inside. Now all the logic is contained in `polygon_generic` which gets a function pointer to the hline handler, so there is no duplicated code anymore. --- libImaging/Draw.c | 132 +++++++--------------------------------------- 1 file changed, 20 insertions(+), 112 deletions(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index f13ba4df0..1241c8d3b 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -61,6 +61,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,8 +406,10 @@ x_cmp(const void *x0, const void *x1) return 0; } + 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 handler) { int i, j; float *xx; @@ -436,22 +441,23 @@ polygon8(Imaging im, int n, Edge *e, int ink, int eofill) for (;ymin <= ymax; ymin++) { y = ymin+0.5F; - for (i = j = 0; i < n; i++) + 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); + (*handler)(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); + (*handler)(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); + (*handler)(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); + (*handler)(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); } } @@ -460,120 +466,22 @@ polygon8(Imaging im, int n, Edge *e, int ink, int eofill) 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 From 8739613cfb3731a1411de09c71161ef217ea2ca1 Mon Sep 17 00:00:00 2001 From: Terseus Date: Tue, 1 Apr 2014 13:08:15 +0200 Subject: [PATCH 002/488] Implementation of rounds around zero The rounds used in the library are towards positive/negative infinity. Since we're in a cartesian plane, rounds around zero are much more convenient, so we can use positive and negative values with consistent results. --- libImaging/Draw.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 1241c8d3b..c29132112 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=torwards zero, down=away from 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 */ /* -------------------------------------------------------------------- */ From cd332fc38a2320207153b73d5fecedee135adb8b Mon Sep 17 00:00:00 2001 From: Terseus Date: Wed, 2 Apr 2014 20:29:56 +0200 Subject: [PATCH 003/488] Rewrite of the polygon_generic function The (previously refactored) polygon_generic function didn't draw consistent polygons (equilateral polygons were not equilateral nor symmetrical). The most notable changes are: * The horizontal edges are searched for when finding the polygon boundaries, drawn and discarded from the edge list used to detect intersections. * The intersections are now checked and calculated from the current value of the scan line (ymin) instead of in the middle (ymin + 0.5). * Because the change in the scan line behavior, we should duplicate the intersections in the maximum Y value of an edge or there will be draw errors with concave and complex polygons. * The rounds of the X coordinates in the hline function calls are switched to draw the inner pixels. * Removed the ugly micro-optimization of qsort at the end. This implementation of the scan line algorithm may not be technically correct, it's not optimized and it have problems with some edge cases, like a wide line from (x0, y) to (x1, y + 1), therefore it should be reviewed in the future. --- libImaging/Draw.c | 93 ++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index c29132112..3d3ecc2a2 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -414,62 +414,71 @@ x_cmp(const void *x0, const void *x1) } +/* + * Filled polygon draw function using scan line algorithm. + */ static inline int polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, - hline_handler handler) + hline_handler hline) { - int i, j; - float *xx; - int ymin, ymax; - float y; - - if (n <= 0) + 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** edge_table = malloc(sizeof(Edge*) * n); + if (!edge_table) { return -1; + } + int edge_count = 0; + int ymin = im->ysize - 1; + int ymax = 0; + int i; + 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; + } - 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) - (*handler)(im, e[i].xmin, ymin, e[i].xmax, ink); - else - xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; + /* Process the edge table with a scan line searching for intersections */ + float* xx = malloc(sizeof(float) * edge_count * 2); + 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++; } } - if (j == 2) { - if (xx[0] < xx[1]) - (*handler)(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); - else - (*handler)(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) - (*handler)(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); + 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; } From be09da65078d1368c27354e4c4f1cf7611cfa2e6 Mon Sep 17 00:00:00 2001 From: Terseus Date: Wed, 2 Apr 2014 21:30:42 +0200 Subject: [PATCH 004/488] Rewrite of the ImagingDrawWideLine function The previous version of the function didn't generate correct wide lines of even width. The most notable changes are: * Make variable names far more descriptive about the process. * Corrected the width calculation; we should deduct one pixel from the width because the pixels at the center of the line doesn't count for the triangles. * Now we calculate *two* ratios, one for left/top displacement (dxmin) and one for right/bottom (dxmax), this fix the behavior with lines of even width. It can probably be optimized. --- libImaging/Draw.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 3d3ecc2a2..a0430652a 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -588,11 +588,6 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, DRAW* draw; INT32 ink; - Edge e[4]; - - int dx, dy; - double d; - DRAWINIT(); if (width <= 1) { @@ -600,23 +595,34 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, return 0; } - dx = x1-x0; - dy = y1-y0; - + int dx = x1-x0; + int 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; + double big_hypotenuse = sqrt((double) (dx*dx + dy*dy)); + double small_hypotenuse = (width - 1) / 2.0; + double ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse; + double ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse; - dx = (int) floor(d * (y1-y0) + 0.5); - dy = (int) floor(d * (x1-x0) + 0.5); + int dxmin = ROUND_DOWN(ratio_min * dy); + int dxmax = ROUND_DOWN(ratio_max * dy); + int dymin = ROUND_DOWN(ratio_min * dx); + int 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); + Edge e[4]; + 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); From dbe3db6fc541e15b7f54c4a1fddf1ceb384de2d2 Mon Sep 17 00:00:00 2001 From: Terseus Date: Thu, 3 Apr 2014 11:15:21 +0200 Subject: [PATCH 005/488] Oops, fixed typo in comments --- libImaging/Draw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index a0430652a..43fe32450 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -50,7 +50,7 @@ (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2)) /* - * Rounds around zero (up=torwards zero, down=away from zero) + * 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))) From 7de14a51dda40f46711f8c20c6ddce6a8bac7713 Mon Sep 17 00:00:00 2001 From: Terseus Date: Fri, 4 Apr 2014 10:27:08 +0200 Subject: [PATCH 006/488] Added base method and constants to test_imagedraw The method `create_base_image_draw` will be used in all the new draw tests. --- Tests/test_imagedraw.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index f8b5c3c5c..ab09d7174 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -2,6 +2,13 @@ from tester import * from PIL import Image 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') def test_sanity(): @@ -26,3 +33,12 @@ def test_deprecated(): assert_warning(DeprecationWarning, lambda: draw.setink(0)) assert_warning(DeprecationWarning, lambda: draw.setfill(0)) + +def create_base_image_draw(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)) + From ec74779b198c26dbf82e42644476e35bd2044850 Mon Sep 17 00:00:00 2001 From: Terseus Date: Fri, 4 Apr 2014 11:28:41 +0200 Subject: [PATCH 007/488] Added test for a simple square --- Tests/images/imagedraw/square.png | Bin 0 -> 135 bytes Tests/test_imagedraw.py | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 Tests/images/imagedraw/square.png diff --git a/Tests/images/imagedraw/square.png b/Tests/images/imagedraw/square.png new file mode 100644 index 0000000000000000000000000000000000000000..842ee4722d52f6a93a4557286ce8412a9d4e0e7d GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih`sfJr))YWBHdw_eFt1YMw5RArhC9Z*0%M|NsC0!ihW;PrkjqeVie*l`)_s*^6K5 dZptrd27!ai9^Q>R`wXaq!PC{xWt~$(69E6ZC-wjU literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ab09d7174..138c14cca 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -42,3 +42,24 @@ def create_base_image_draw(size, mode=DEFAULT_MODE, background1=WHITE, backgroun img.putpixel((x, y), background2) return (img, ImageDraw.Draw(img)) + +def test_square(): + expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) + expected.load() + # Normal polygon + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + assert_image_equal(img, expected) + # Inverted polygon + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + assert_image_equal(img, expected) + # Normal rectangle + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + assert_image_equal(img, expected) + # Inverted rectangle + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + assert_image_equal(img, expected) + From e2cb2195eb3bc90c1f3bc47a375802d8416f804a Mon Sep 17 00:00:00 2001 From: Terseus Date: Fri, 4 Apr 2014 11:46:58 +0200 Subject: [PATCH 008/488] Added test for a simple right triangle The diagonals of the right angled edges must be perfect and the bottom vertice should be drawn. --- Tests/images/imagedraw/triangle_right.png | Bin 0 -> 168 bytes Tests/test_imagedraw.py | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 Tests/images/imagedraw/triangle_right.png diff --git a/Tests/images/imagedraw/triangle_right.png b/Tests/images/imagedraw/triangle_right.png new file mode 100644 index 0000000000000000000000000000000000000000..e91fa580234157bcbe1784671e6b3a89d36f7fef GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;O(OXD=}PE(+eyQhm|h{fsT8{6~m|NsC0q_X6RYdiDr+qEssGiKIg=sv6R z^2np~N2N*MjVD?awwW1PeQY&zoXhe^a=+i9k3H_sJN$1upGsrkyqq5t@`6nTXe5KD LtDnm{r-UW|g`qaL literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 138c14cca..45f9d5f03 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -63,3 +63,10 @@ def test_square(): draw.rectangle((7, 7, 2, 2), BLACK) assert_image_equal(img, expected) + +def test_triangle_right(): + expected = Image.open(os.path.join(IMAGES_PATH, 'triangle_right.png')) + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + assert_image_equal(img, expected) From fee2faa8dc7e5c552bd389e399956b7abf9bde4f Mon Sep 17 00:00:00 2001 From: Terseus Date: Fri, 4 Apr 2014 12:35:26 +0200 Subject: [PATCH 009/488] Added test for horizontal lines Notice that the expansion of the line width depends on the order of the points: * If the bigger axis value is provided as the *second* point the line expand first to the *positive* side of the axis. * If the bigger axis value is provided as the *first* point the line expand first to the *negative* side of the axis. * If the line's width is odd this doesn't matter, as the line will expand the same amount to both sides. This behavior should be consistent in both horizontal and vertical lines. --- .../line_horizontal_w2px_inverted.png | Bin 0 -> 143 bytes .../imagedraw/line_horizontal_w2px_normal.png | Bin 0 -> 141 bytes .../images/imagedraw/line_horizontal_w3px.png | Bin 0 -> 145 bytes Tests/test_imagedraw.py | 25 ++++++++++++++++++ 4 files changed, 25 insertions(+) create mode 100644 Tests/images/imagedraw/line_horizontal_w2px_inverted.png create mode 100644 Tests/images/imagedraw/line_horizontal_w2px_normal.png create mode 100644 Tests/images/imagedraw/line_horizontal_w3px.png 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 0000000000000000000000000000000000000000..a2bb3bbabe8681bda838121298492ec0fa0ec7d4 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;Pclf$Pc;3!Z?&(p;*#Nu@FjqUmO|NsAgQd#oEwVnC*?b;lZjhW*d4y65= l#lCjFd#>Ep${ja&7^0@%XP8{HC?2Sj!PC{xWt~$(697S^DlGs2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a6d409ea48aa491f96ca5e7df6b95d10bb08a255 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;Pc!;ovz%O0SRj;D)bh{fsT8{6~m|NsC0q_X6RYdiDr+qFGRG-i%-IFR;d jmb-KHm1lvoIVUrmh`i6R!7BRFdgVkA;O(nKNvekP=YHz|+Ms#Nu@FjqUmO|NsAgQd#oEwVnC*?b;lZjhW*d4y65= nm6`cjfcI_SZ1>z{>&`OdUpw;lGq3h@pk4+~S3j3^P6 Date: Fri, 4 Apr 2014 12:45:47 +0200 Subject: [PATCH 010/488] Added test for vertical lines. The behavior is consistent with horizontal lines, see previous commit for details. --- .../imagedraw/line_vertical_w2px_inverted.png | Bin 0 -> 145 bytes .../imagedraw/line_vertical_w2px_normal.png | Bin 0 -> 144 bytes Tests/images/imagedraw/line_vertical_w3px.png | Bin 0 -> 142 bytes Tests/test_imagedraw.py | 25 ++++++++++++++++++ 4 files changed, 25 insertions(+) create mode 100644 Tests/images/imagedraw/line_vertical_w2px_inverted.png create mode 100644 Tests/images/imagedraw/line_vertical_w2px_normal.png create mode 100644 Tests/images/imagedraw/line_vertical_w3px.png 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 0000000000000000000000000000000000000000..74d666b885efa5adfdb95a9bc58da19afff46b38 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;PcSH0PA#SEa3fv1aOh{fsT8{6~m|NsC0q_X6RYdiDr+qESo7&C*xOfzSb j{#!O#pEK4iI+4auC~==*SFdgVkA;Pc$Gm-R=U$+YzNd?0h{fr*mlp~$I0zg%@FhMZ>hY2ChCc@%7_D~jy`)>Z m@>4qx!_q&GPEP*5D$b5~=6weL$V+BGtqh*7elF{r5}E*%{45p# literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw/line_vertical_w3px.png b/Tests/images/imagedraw/line_vertical_w3px.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5234f2a8a9a66a2a13bfc12c56723c5e3a4883 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;O(#c0Kw+=D Date: Fri, 4 Apr 2014 14:02:36 +0200 Subject: [PATCH 011/488] Added missing xx pointer check --- libImaging/Draw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 43fe32450..8daf0e2c0 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -458,6 +458,9 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, /* Process the edge table with a scan line searching for intersections */ float* xx = malloc(sizeof(float) * edge_count * 2); + if (!xx) { + return -1; + } for (; ymin <= ymax; ymin++) { int j = 0; for (i = 0; i < edge_count; i++) { From 2f6a4d5f1a693bf69a04032dd43dc550a7de2230 Mon Sep 17 00:00:00 2001 From: Terseus Date: Fri, 4 Apr 2014 18:57:08 +0200 Subject: [PATCH 012/488] Added missing free when xx fail --- libImaging/Draw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 8daf0e2c0..ad8d589af 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -459,6 +459,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, /* Process the edge table with a scan line searching for intersections */ float* xx = malloc(sizeof(float) * edge_count * 2); if (!xx) { + free(edge_table) return -1; } for (; ymin <= ymax; ymin++) { From 8eae39e98ff72c561d78c40362c6a6d39e77ac3b Mon Sep 17 00:00:00 2001 From: Terseus Date: Fri, 4 Apr 2014 19:20:29 +0200 Subject: [PATCH 013/488] Oops, added missing ';' Now writing in the wall "don't push before compile" 100 times. --- libImaging/Draw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index ad8d589af..2f53fde79 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -459,7 +459,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, /* Process the edge table with a scan line searching for intersections */ float* xx = malloc(sizeof(float) * edge_count * 2); if (!xx) { - free(edge_table) + free(edge_table); return -1; } for (; ymin <= ymax; ymin++) { From 92dd58c01464d74dcfea7a092a80da5cdfd1667c Mon Sep 17 00:00:00 2001 From: Terseus Date: Sun, 6 Apr 2014 13:57:34 +0200 Subject: [PATCH 014/488] Added descriptive errors to imagedraw tests --- Tests/test_imagedraw.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index e42a2f054..ddf2fd68f 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -46,22 +46,18 @@ def create_base_image_draw(size, mode=DEFAULT_MODE, background1=WHITE, backgroun def test_square(): expected = Image.open(os.path.join(IMAGES_PATH, 'square.png')) expected.load() - # Normal polygon img, draw = create_base_image_draw((10, 10)) draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) - assert_image_equal(img, expected) - # Inverted polygon + assert_image_equal(img, expected, 'square as normal polygon failed') img, draw = create_base_image_draw((10, 10)) draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) - assert_image_equal(img, expected) - # Normal rectangle + assert_image_equal(img, expected, 'square as inverted polygon failed') img, draw = create_base_image_draw((10, 10)) draw.rectangle((2, 2, 7, 7), BLACK) - assert_image_equal(img, expected) - # Inverted rectangle + assert_image_equal(img, expected, 'square as normal rectangle failed') img, draw = create_base_image_draw((10, 10)) draw.rectangle((7, 7, 2, 2), BLACK) - assert_image_equal(img, expected) + assert_image_equal(img, expected, 'square as inverted rectangle failed') def test_triangle_right(): @@ -69,54 +65,46 @@ def test_triangle_right(): expected.load() img, draw = create_base_image_draw((20, 20)) draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) - assert_image_equal(img, expected) + assert_image_equal(img, expected, 'triangle right failed') def test_line_horizontal(): - # Normal 2px line expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_w2px_normal.png')) expected.load() img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 5), BLACK, 2) - assert_image_equal(img, expected) - # Inverted 2px line + 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 = create_base_image_draw((20, 20)) draw.line((14, 5, 5, 5), BLACK, 2) - assert_image_equal(img, expected) - # Normal 3px line + 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 = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 5), BLACK, 3) - assert_image_equal(img, expected) - # Inverted 3px line + assert_image_equal(img, expected, 'line straigth horizontal normal 3px wide failed') img, draw = create_base_image_draw((20, 20)) draw.line((14, 5, 5, 5), BLACK, 3) - assert_image_equal(img, expected) + assert_image_equal(img, expected, 'line straigth horizontal inverted 3px wide failed') def test_line_vertical(): - # Normal 2px line expected = Image.open(os.path.join(IMAGES_PATH, 'line_vertical_w2px_normal.png')) expected.load() img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 5, 14), BLACK, 2) - assert_image_equal(img, expected) - # Inverted 2px line + 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 = create_base_image_draw((20, 20)) draw.line((5, 14, 5, 5), BLACK, 2) - assert_image_equal(img, expected) - # Normal 3px line + 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 = create_base_image_draw((20, 20)) draw.line((5, 5, 5, 14), BLACK, 3) - assert_image_equal(img, expected) - # Inverted 3px line + assert_image_equal(img, expected, 'line straigth vertical normal 3px wide failed') img, draw = create_base_image_draw((20, 20)) draw.line((5, 14, 5, 5), BLACK, 3) - assert_image_equal(img, expected) + assert_image_equal(img, expected, 'line straigth vertical inverted 3px wide failed') From 8228caf14daf94c5bd970edcc9344fa076184075 Mon Sep 17 00:00:00 2001 From: Terseus Date: Wed, 9 Apr 2014 17:39:52 +0200 Subject: [PATCH 015/488] =?UTF-8?q?Added=20some=20oblique=2045=C2=BA=20lin?= =?UTF-8?q?es=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the oblique 3 pixels wide lines are defined: * The oblique 2 pixels wide lines are somewhat hard to define. * To define the oblique lines wider than 3 pixels we neet to define first how the oblique lines should expand their width (realistic or exact). --- .../imagedraw/line_oblique_45_w3px_a.png | Bin 0 -> 182 bytes .../imagedraw/line_oblique_45_w3px_b.png | Bin 0 -> 179 bytes Tests/test_imagedraw.py | 19 ++++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 Tests/images/imagedraw/line_oblique_45_w3px_a.png create mode 100644 Tests/images/imagedraw/line_oblique_45_w3px_b.png 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 0000000000000000000000000000000000000000..55c535e29c7970fedfaf710ca1a8cc67efcf47ce GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;(8nm=yu`evX|sHcl#h{frrlMVR}7;rGZ|KI=V`iUtEgA~>+_exFZ^mA`< z3<^!1J7da`>1P}=(ynJ4pFee$um0cWlk@NU?7O@9aDVXKAKr)3?%i-dAAR%OS?9E$ b4{i06g=S3gTe~DWM4fJi9@C literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8c1036559cc72b285623cfbcb7559d64c5c7801e GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA;(8#q#7$0X3jdkf)1dh{frvm#u{u6gZp@-uWM}Qz}f%>V$goLXndEZHyC4 zjB}m!JRem`HfSXKMIW=`wbT%{)Kj+9?JrvT Date: Wed, 9 Apr 2014 19:05:31 +0200 Subject: [PATCH 016/488] Added tests of hor/ver lines 101px wide These tests should guarantee that the proportion of the width is maintained with a margin of error < 1%. --- Tests/images/imagedraw/line_horizontal_w101px.png | Bin 0 -> 368 bytes Tests/images/imagedraw/line_vertical_w101px.png | Bin 0 -> 438 bytes Tests/test_imagedraw.py | 10 ++++++++++ 3 files changed, 10 insertions(+) create mode 100644 Tests/images/imagedraw/line_horizontal_w101px.png create mode 100644 Tests/images/imagedraw/line_vertical_w101px.png diff --git a/Tests/images/imagedraw/line_horizontal_w101px.png b/Tests/images/imagedraw/line_horizontal_w101px.png new file mode 100644 index 0000000000000000000000000000000000000000..bbcc8133fd3975eb00d5625a97e3df0ed176ca9a GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGQg9%8!tv_uAq&N#aB8wRqxP?KOkzv*x37{Z* ziKnkC`#ly;K?e5sFOmWo7#R6IT^vIyZoR#-ke9)L!`1PdeAm?#>sDqsOt{lIKS5C6 zX!EKyZ|{C!wchxUJ8a{cq{ySY@_u~Pn)d#K@5TTf5$>ZwAi`-Qmb22ChI3N5He`VXYGKC+O9OtZ;G#IR0 z^={j9Y5hC5DjL1C{s&5~H3@z>cj<~Hlf1NCRi}mod4dQ{t(71F5D^F#01;gf0ib9Q zR2fio5lEQ`TnjO(!6t*WKum_%4Ym*L0Fd1f3&3U*Pz^Q%w{t-Tk?FagE4+5^{dj2Y e(n+VjmoZG2pILd>QkD}K<_w;$elF{r5}E*{Dxzxu literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index defb3805a..46bb43a11 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -87,6 +87,11 @@ def test_line_horizontal(): img, draw = create_base_image_draw((20, 20)) draw.line((14, 5, 5, 5), BLACK, 3) 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 = create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + assert_image_equal(img, expected, 'line straigth horizontal 101px wide failed') def test_line_vertical(): @@ -108,6 +113,11 @@ def test_line_vertical(): img, draw = create_base_image_draw((20, 20)) draw.line((5, 14, 5, 5), BLACK, 3) 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 = create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + assert_image_equal(img, expected, 'line straigth vertical 101px wide failed') def test_line_oblique_45(): From b987d90568ec3662c0c05085412b2f2b4a1c519f Mon Sep 17 00:00:00 2001 From: Terseus Date: Wed, 9 Apr 2014 19:12:03 +0200 Subject: [PATCH 017/488] Added tests for lines with 1px slope This tests are designed to guarantee that the wide lines behave exactly like normal lines drawn with the Bresenham's algorithm. This tests are somewhat subjective since this is non-defined behavior, but I think that mimic the Bresenham's algorithm is reliable enough. Currently the horizontal version of this test **fail**. --- .../imagedraw/line_horizontal_slope1px_w2px.png | Bin 0 -> 147 bytes .../imagedraw/line_vertical_slope1px_w2px.png | Bin 0 -> 153 bytes Tests/test_imagedraw.py | 10 ++++++++++ 3 files changed, 10 insertions(+) create mode 100644 Tests/images/imagedraw/line_horizontal_slope1px_w2px.png create mode 100644 Tests/images/imagedraw/line_vertical_slope1px_w2px.png 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 0000000000000000000000000000000000000000..aaed7678680d7e413d23532b97d391a3611e53b9 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA+h}LUew|!*ZaIk*AAeh{fsT8{6~m|NsC0q_X6RYdiDr+qFGRG-lRPIGe_{ pr@_R2ai-k7g|=CrSDcklVbDErpTW3kSprZugQu&X%Q~loCIB4yE7kx2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1d48a57e87705b5d9f774abf10cc736201ae0c9f GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1SFYWcSQjy&H|6fVg?3oVGw3ym^DWND9B#o z>FdgVkA+h}M#(v{u?8q);pyTSVsZNEWJMtc1D@s|{}=y=QcqLAcH-KR!i1)%(3?V& wwT#a&2+Y#obNbFB`PTnWi_g4{yLyW`PWnDW#_#1n+a literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 46bb43a11..6b4e74480 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -92,6 +92,11 @@ def test_line_horizontal(): img, draw = create_base_image_draw((200, 110)) draw.line((5, 55, 195, 55), BLACK, 101) assert_image_equal(img, expected, 'line straigth horizontal 101px wide failed') + expected = Image.open(os.path.join(IMAGES_PATH, 'line_horizontal_slope1px_w2px.png')) + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + assert_image_equal(img, expected, 'line horizontal 1px slope 2px wide failed') def test_line_vertical(): @@ -118,6 +123,11 @@ def test_line_vertical(): img, draw = create_base_image_draw((110, 200)) draw.line((55, 5, 55, 195), BLACK, 101) 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 = create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + assert_image_equal(img, expected, 'line vertical 1px slope 2px wide failed') def test_line_oblique_45(): From 113b8c2a7633edb39894b3f3818e2e5b0e871f96 Mon Sep 17 00:00:00 2001 From: Terseus Date: Wed, 9 Apr 2014 20:06:58 +0200 Subject: [PATCH 018/488] Removed non-ASCII character from messages --- Tests/test_imagedraw.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 6b4e74480..76e602f54 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -135,15 +135,15 @@ def test_line_oblique_45(): expected.load() img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 14), BLACK, 3) - assert_image_equal(img, expected, 'line oblique 45º normal 3px wide A failed') + assert_image_equal(img, expected, 'line oblique 45 normal 3px wide A failed') img, draw = create_base_image_draw((20, 20)) draw.line((14, 14, 5, 5), BLACK, 3) - assert_image_equal(img, expected, 'line oblique 45º inverted 3px wide A failed') + 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 = create_base_image_draw((20, 20)) draw.line((14, 5, 5, 14), BLACK, 3) - assert_image_equal(img, expected, 'line oblique 45º normal 3px wide B failed') + assert_image_equal(img, expected, 'line oblique 45 normal 3px wide B failed') img, draw = create_base_image_draw((20, 20)) draw.line((5, 14, 14, 5), BLACK, 3) - assert_image_equal(img, expected, 'line oblique 45º inverted 3px wide B failed') + assert_image_equal(img, expected, 'line oblique 45 inverted 3px wide B failed') From 5cd454bde27eab02dbcfc0adf0657a018cf5db47 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 14 Apr 2014 12:30:32 +0300 Subject: [PATCH 019/488] Fix docstring printing from __main__, and pyflakes and some pep8 --- PIL/ImageCms.py | 154 +++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 67 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index c875712c1..5decaf704 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -1,19 +1,19 @@ -# -# The Python Imaging Library. -# $Id$ -# -# optional color managment support, based on Kevin Cazabon's PyCMS -# library. -# -# History: -# 2009-03-08 fl Added to PIL. -# -# Copyright (C) 2002-2003 Kevin Cazabon -# Copyright (c) 2009 by Fredrik Lundh -# -# See the README file for information on usage and redistribution. See -# below for the original description. -# +""" +The Python Imaging Library. +$Id$ + +Optional color managment support, based on Kevin Cazabon's PyCMS +library. + +History: +2009-03-08 fl Added to PIL. + +Copyright (C) 2002-2003 Kevin Cazabon +Copyright (c) 2009 by Fredrik Lundh + +See the README file for information on usage and redistribution. See +below for the original description. +""" from __future__ import print_function @@ -88,7 +88,7 @@ try: from PIL import _imagingcms except ImportError as ex: # Allow error import for doc purposes, but error out when accessing - # anything in core. + # anything in core. from _util import import_err _imagingcms = import_err(ex) from PIL._util import isStringType @@ -113,22 +113,22 @@ DIRECTION_PROOF = 2 FLAGS = { "MATRIXINPUT": 1, "MATRIXOUTPUT": 2, - "MATRIXONLY": (1|2), - "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot - "NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) - "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) - "NOTCACHE": 64, # Inhibit 1-pixel cache + "MATRIXONLY": (1 | 2), + "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot + "NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) + "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) + "NOTCACHE": 64, # Inhibit 1-pixel cache "NOTPRECALC": 256, - "NULLTRANSFORM": 512, # Don't transform anyway - "HIGHRESPRECALC": 1024, # Use more memory to give better accurancy - "LOWRESPRECALC": 2048, # Use less memory to minimize resouces + "NULLTRANSFORM": 512, # Don't transform anyway + "HIGHRESPRECALC": 1024, # Use more memory to give better accurancy + "LOWRESPRECALC": 2048, # Use less memory to minimize resouces "WHITEBLACKCOMPENSATION": 8192, "BLACKPOINTCOMPENSATION": 8192, - "GAMUTCHECK": 4096, # Out of Gamut alarm - "SOFTPROOFING": 16384, # Do softproofing - "PRESERVEBLACK": 32768, # Black preservation - "NODEFAULTRESOURCEDEF": 16777216, # CRD special - "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints + "GAMUTCHECK": 4096, # Out of Gamut alarm + "SOFTPROOFING": 16384, # Do softproofing + "PRESERVEBLACK": 32768, # Black preservation + "NODEFAULTRESOURCEDEF": 16777216, # CRD special + "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints } _MAX_FLAG = 0 @@ -136,6 +136,7 @@ for flag in FLAGS.values(): if isinstance(flag, int): _MAX_FLAG = _MAX_FLAG | flag + # --------------------------------------------------------------------. # Experimental PIL-level API # --------------------------------------------------------------------. @@ -153,18 +154,19 @@ class ImageCmsProfile: elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) else: - self._set(profile) # assume it's already a profile + self._set(profile) # assume it's already a profile def _set(self, profile, filename=None): self.profile = profile self.filename = filename if profile: - self.product_name = None #profile.product_name - self.product_info = None #profile.product_info + self.product_name = None # profile.product_name + self.product_info = None # profile.product_info else: self.product_name = None self.product_info = None + class ImageCmsTransform(Image.ImagePointHandler): """Transform. This can be used with the procedural API, or with the standard Image.point() method. @@ -179,14 +181,14 @@ class ImageCmsTransform(Image.ImagePointHandler): input_mode, output_mode, intent, flags - ) + ) else: self.transform = core.buildProofTransform( input.profile, output.profile, proof.profile, input_mode, output_mode, intent, proof_intent, flags - ) + ) # Note: inputMode and outputMode are for pyCMS compatibility only self.input_mode = self.inputMode = input_mode self.output_mode = self.outputMode = output_mode @@ -198,21 +200,22 @@ class ImageCmsTransform(Image.ImagePointHandler): im.load() if imOut is None: imOut = Image.new(self.output_mode, im.size, None) - result = self.transform.apply(im.im.id, imOut.im.id) + self.transform.apply(im.im.id, imOut.im.id) return imOut def apply_in_place(self, im): im.load() if im.mode != self.output_mode: - raise ValueError("mode mismatch") # wrong output mode - result = self.transform.apply(im.im.id, im.im.id) + raise ValueError("mode mismatch") # wrong output mode + self.transform.apply(im.im.id, im.im.id) return im + def get_display_profile(handle=None): """ (experimental) Fetches the profile for the current display device. :returns: None if the profile is not known. """ - + import sys if sys.platform == "win32": from PIL import ImageWin @@ -229,6 +232,7 @@ def get_display_profile(handle=None): profile = get() return ImageCmsProfile(profile) + # --------------------------------------------------------------------. # pyCMS compatible layer # --------------------------------------------------------------------. @@ -237,6 +241,7 @@ class PyCMSError(Exception): """ (pyCMS) Exception class. This is used for all errors in the pyCMS API. """ pass + def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0): """ (pyCMS) Applies an ICC transformation to a given image, mapping from @@ -288,7 +293,7 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER if outputMode is None: outputMode = im.mode - if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): @@ -300,8 +305,9 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER if not isinstance(outputProfile, ImageCmsProfile): outputProfile = ImageCmsProfile(outputProfile) transform = ImageCmsTransform( - inputProfile, outputProfile, im.mode, outputMode, renderingIntent, flags=flags - ) + inputProfile, outputProfile, im.mode, outputMode, + renderingIntent, flags=flags + ) if inPlace: transform.apply_in_place(im) imOut = None @@ -334,6 +340,7 @@ def getOpenProfile(profileFilename): except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the @@ -389,7 +396,7 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent :exception PyCMSError: """ - if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): @@ -404,6 +411,7 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the @@ -476,8 +484,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo :returns: A CmsTransform class object. :exception PyCMSError: """ - - if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): @@ -497,6 +505,7 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo buildTransformFromOpenProfiles = buildTransform buildProofTransformFromOpenProfiles = buildProofTransform + def applyTransform(im, transform, inPlace=0): """ (pyCMS) Applies a transform to a given image. @@ -546,6 +555,7 @@ def applyTransform(im, transform, inPlace=0): return imOut + def createProfile(colorSpace, colorTemp=-1): """ (pyCMS) Creates a profile. @@ -586,6 +596,7 @@ def createProfile(colorSpace, colorTemp=-1): except (TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileName(profile): """ @@ -612,14 +623,14 @@ def getProfileName(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) # do it in python, not c. - # // name was "%s - %s" (model, manufacturer) || Description , - # // but if the Model and Manufacturer were the same or the model + # // name was "%s - %s" (model, manufacturer) || Description , + # // but if the Model and Manufacturer were the same or the model # // was long, Just the model, in 1.x model = profile.profile.product_model manufacturer = profile.profile.product_manufacturer if not (model or manufacturer): - return profile.profile.product_description+"\n" + return profile.profile.product_description + "\n" if not manufacturer or len(model) > 30: return model + "\n" return "%s - %s\n" % (model, manufacturer) @@ -627,6 +638,7 @@ def getProfileName(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileInfo(profile): """ (pyCMS) Gets the internal product information for the given profile. @@ -647,7 +659,7 @@ def getProfileInfo(profile): tag. :exception PyCMSError: """ - + try: if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) @@ -660,7 +672,7 @@ def getProfileInfo(profile): for elt in (description, cpright): if elt: arr.append(elt) - return "\r\n\r\n".join(arr)+"\r\n\r\n" + return "\r\n\r\n".join(arr) + "\r\n\r\n" except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -677,7 +689,7 @@ def getProfileCopyright(profile): is raised Use this function to obtain the information stored in the profile's - copyright tag. + copyright tag. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -693,6 +705,7 @@ def getProfileCopyright(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileManufacturer(profile): """ (pyCMS) Gets the manufacturer for the given profile. @@ -704,7 +717,7 @@ def getProfileManufacturer(profile): is raised Use this function to obtain the information stored in the profile's - manufacturer tag. + manufacturer tag. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -720,19 +733,20 @@ def getProfileManufacturer(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileModel(profile): """ (pyCMS) Gets the model for the given profile. - + If profile isn't a valid CmsProfile object or filename to a profile, a PyCMSError is raised. - + If an error occurs while trying to obtain the model tag, a PyCMSError is raised - + Use this function to obtain the information stored in the profile's - model tag. - + model tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. :returns: A string containing the internal profile information stored in an ICC @@ -748,6 +762,7 @@ def getProfileModel(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileDescription(profile): """ (pyCMS) Gets the description for the given profile. @@ -759,7 +774,7 @@ def getProfileDescription(profile): is raised Use this function to obtain the information stored in the profile's - description tag. + description tag. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -813,6 +828,7 @@ def getDefaultIntent(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def isIntentSupported(profile, intent, direction): """ (pyCMS) Checks if a given intent is supported. @@ -862,15 +878,17 @@ def isIntentSupported(profile, intent, direction): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def versions(): """ (pyCMS) Fetches versions. """ - + import sys return ( - VERSION, core.littlecms_version, sys.version.split()[0], Image.VERSION - ) + VERSION, core.littlecms_version, + sys.version.split()[0], Image.VERSION + ) # -------------------------------------------------------------------- @@ -880,14 +898,16 @@ if __name__ == "__main__": from PIL import ImageCms print(__doc__) - for f in dir(pyCMS): - print("="*80) - print("%s" %f) - + for f in dir(ImageCms): + doc = None try: - exec ("doc = ImageCms.%s.__doc__" %(f)) + exec("doc = %s.__doc__" % (f)) if "pyCMS" in doc: # so we don't get the __doc__ string for imported modules + print("=" * 80) + print("%s" % f) print(doc) - except AttributeError: + except (AttributeError, TypeError): pass + +# End of file From a97e5039d83bdb862b577196dc0736173f15f09a Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 14 Apr 2014 12:51:12 +0300 Subject: [PATCH 020/488] Remove unused _binary import (plus flake8) --- PIL/Jpeg2KImagePlugin.py | 41 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index f57f4a784..ee8fc46aa 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -15,20 +15,21 @@ __version__ = "0.1" -from PIL import Image, ImageFile, _binary +from PIL import Image, ImageFile import struct import os import io + def _parse_codestream(fp): """Parse the JPEG 2000 codestream to extract the size and component count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" - + hdr = fp.read(2) lsiz = struct.unpack('>H', hdr)[0] siz = hdr + fp.read(lsiz - 2) lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \ - xtosiz, ytosiz, csiz \ + xtosiz, ytosiz, csiz \ = struct.unpack('>HHIIIIIIIIH', siz[:38]) ssiz = [None]*csiz xrsiz = [None]*csiz @@ -48,13 +49,14 @@ def _parse_codestream(fp): mode == 'RGBA' else: mode = None - + return (size, mode) + def _parse_jp2_header(fp): """Parse the JP2 header box to extract size, component count and color space information, returning a PIL (size, mode) tuple.""" - + # Find the JP2 header box header = None while True: @@ -76,7 +78,7 @@ def _parse_jp2_header(fp): size = None mode = None - + hio = io.BytesIO(header) while True: lbox, tbox = struct.unpack('>I4s', hio.read(8)) @@ -90,7 +92,7 @@ def _parse_jp2_header(fp): if tbox == b'ihdr': height, width, nc, bpc, c, unkc, ipr \ - = struct.unpack('>IIHBBBB', content) + = struct.unpack('>IIHBBBB', content) size = (width, height) if unkc: if nc == 1: @@ -112,13 +114,13 @@ def _parse_jp2_header(fp): elif nc == 4: mode = 'RGBA' break - elif cs == 17: # grayscale + elif cs == 17: # grayscale if nc == 1: mode = 'L' elif nc == 2: mode = 'LA' break - elif cs == 18: # sYCC + elif cs == 18: # sYCC if nc == 3: mode = 'RGB' elif nc == 4: @@ -127,6 +129,7 @@ def _parse_jp2_header(fp): return (size, mode) + ## # Image plugin for JPEG2000 images. @@ -141,16 +144,16 @@ class Jpeg2KImageFile(ImageFile.ImageFile): self.size, self.mode = _parse_codestream(self.fp) else: sig = sig + self.fp.read(8) - + if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': self.codec = "jp2" self.size, self.mode = _parse_jp2_header(self.fp) else: raise SyntaxError('not a JPEG 2000 file') - + if self.size is None or self.mode is None: raise SyntaxError('unable to determine size/mode') - + self.reduce = 0 self.layers = 0 @@ -177,13 +180,15 @@ class Jpeg2KImageFile(ImageFile.ImageFile): t = self.tile[0] t3 = (t[3][0], self.reduce, self.layers, t[3][3]) self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] - + ImageFile.ImageFile.load(self) - + + def _accept(prefix): return (prefix[:4] == b'\xff\x4f\xff\x51' or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') + # ------------------------------------------------------------ # Save support @@ -214,7 +219,7 @@ def _save(im, fp, filename): fd = fp.fileno() except: fd = -1 - + im.encoderconfig = ( offset, tile_offset, @@ -228,10 +233,10 @@ def _save(im, fp, filename): progression, cinema_mode, fd - ) - + ) + ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) - + # ------------------------------------------------------------ # Registry stuff From 329fd00d3fe2709c043c73e075da03b6d9729a2b Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 14 Apr 2014 13:49:29 +0300 Subject: [PATCH 021/488] Test j2k --- Tests/images/rgb_trns_ycbc.j2k | Bin 0 -> 5365 bytes Tests/images/rgb_trns_ycbc.jp2 | Bin 0 -> 5450 bytes Tests/test_file_jpeg2k.py | 36 ++++++++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 Tests/images/rgb_trns_ycbc.j2k create mode 100644 Tests/images/rgb_trns_ycbc.jp2 diff --git a/Tests/images/rgb_trns_ycbc.j2k b/Tests/images/rgb_trns_ycbc.j2k new file mode 100644 index 0000000000000000000000000000000000000000..462729501e184f82e36a5dc9ea9548c9dca6a4ea GIT binary patch literal 5365 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 literal 0 HcmV?d00001 diff --git a/Tests/images/rgb_trns_ycbc.jp2 b/Tests/images/rgb_trns_ycbc.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..dea77c6dab27083254984681d6b79ff2ce73619b GIT binary patch literal 5450 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b11e5e6ab..628c53437 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,7 +1,6 @@ from tester import * from PIL import Image -from PIL import ImageFile codecs = dir(Image.core) @@ -15,18 +14,20 @@ ignore('Not enough memory to handle tile data') test_card = Image.open('Tests/images/test-card.png') test_card.load() + def roundtrip(im, **options): out = BytesIO() im.save(out, "JPEG2000", **options) bytes = out.tell() out.seek(0) im = Image.open(out) - im.bytes = bytes # for testing only + im.bytes = bytes # for testing only im.load() return im # ---------------------------------------------------------------------- + def test_sanity(): # Internal version number assert_match(Image.core.jp2klib_version, '\d+\.\d+\.\d+$') @@ -36,9 +37,10 @@ def test_sanity(): assert_equal(im.mode, 'RGB') assert_equal(im.size, (640, 480)) assert_equal(im.format, 'JPEG2000') - + # ---------------------------------------------------------------------- + # These two test pre-written JPEG 2000 files that were not written with # PIL (they were made using Adobe Photoshop) @@ -48,6 +50,7 @@ def test_lossless(): im.save('/tmp/test-card.png') assert_image_similar(im, test_card, 1.0e-3) + def test_lossy_tiled(): im = Image.open('Tests/images/test-card-lossy-tiled.jp2') im.load() @@ -55,49 +58,58 @@ def test_lossy_tiled(): # ---------------------------------------------------------------------- + def test_lossless_rt(): im = roundtrip(test_card) assert_image_equal(im, test_card) + def test_lossy_rt(): im = roundtrip(test_card, quality_layers=[20]) assert_image_similar(im, test_card, 2.0) + def test_tiled_rt(): im = roundtrip(test_card, tile_size=(128, 128)) assert_image_equal(im, test_card) + def test_tiled_offset_rt(): im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) assert_image_equal(im, test_card) - + + def test_irreversible_rt(): im = roundtrip(test_card, irreversible=True, quality_layers=[20]) assert_image_similar(im, test_card, 2.0) + def test_prog_qual_rt(): im = roundtrip(test_card, quality_layers=[60, 40, 20], progression='LRCP') assert_image_similar(im, test_card, 2.0) + def test_prog_res_rt(): im = roundtrip(test_card, num_resolutions=8, progression='RLCP') assert_image_equal(im, test_card) # ---------------------------------------------------------------------- + def test_reduce(): im = Image.open('Tests/images/test-card-lossless.jp2') im.reduce = 2 im.load() assert_equal(im.size, (160, 120)) + def test_layers(): out = BytesIO() test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10], progression='LRCP') out.seek(0) - + im = Image.open(out) im.layers = 1 im.load() @@ -108,3 +120,17 @@ def test_layers(): im.layers = 3 im.load() assert_image_similar(im, test_card, 0.4) + + +def test_rgba(): + # Arrange + j2k = Image.open('Tests/images/rgb_trns_ycbc.j2k') + jp2 = Image.open('Tests/images/rgb_trns_ycbc.jp2') + + # Act + j2k.load() + jp2.load() + + # Assert + assert_equal(j2k.mode, 'RGBA') + assert_equal(jp2.mode, 'RGBA') From d5b09509beb7552e651cbe1db1aeb0f873a490e4 Mon Sep 17 00:00:00 2001 From: Dmitry Selitsky Date: Wed, 16 Apr 2014 16:07:43 +0300 Subject: [PATCH 022/488] logic typo fix --- PIL/Jpeg2KImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index f57f4a784..128248512 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -45,7 +45,7 @@ def _parse_codestream(fp): elif csiz == 3: mode = 'RGB' elif csiz == 4: - mode == 'RGBA' + mode = 'RGBA' else: mode = None @@ -122,7 +122,7 @@ def _parse_jp2_header(fp): if nc == 3: mode = 'RGB' elif nc == 4: - mode == 'RGBA' + mode = 'RGBA' break return (size, mode) From 54173d2c67bc8ed8953b56e67d4719676f43a073 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 9 May 2014 21:36:15 -0700 Subject: [PATCH 023/488] Cherry-pick of portions of patch a500ca13933183fa845f09762fc2948fbfe409f1, many shortcut ops added, no functional changes --- PIL/ArgImagePlugin.py | 2 +- PIL/BdfFontFile.py | 2 +- PIL/EpsImagePlugin.py | 4 ++-- PIL/ExifTags.py | 7 ++----- PIL/FliImagePlugin.py | 2 +- PIL/FontFile.py | 4 ++-- PIL/FpxImagePlugin.py | 2 +- PIL/GimpGradientFile.py | 2 +- PIL/GimpPaletteFile.py | 2 +- PIL/IcnsImagePlugin.py | 8 ++++---- PIL/ImImagePlugin.py | 2 +- PIL/Image.py | 5 ++--- PIL/ImageFile.py | 2 +- PIL/ImageMath.py | 1 - PIL/ImageOps.py | 4 ++-- PIL/ImageShow.py | 4 ++-- PIL/ImageStat.py | 5 ++--- PIL/IptcImagePlugin.py | 14 +++++++------- PIL/Jpeg2KImagePlugin.py | 2 +- PIL/JpegImagePlugin.py | 4 ++-- PIL/MpegImagePlugin.py | 4 ++-- PIL/PalmImagePlugin.py | 8 ++++---- PIL/PcxImagePlugin.py | 2 +- PIL/PdfImagePlugin.py | 4 ++-- PIL/PsdImagePlugin.py | 8 ++++---- PIL/TiffImagePlugin.py | 12 ++++++------ PIL/TiffTags.py | 4 ++-- PIL/WmfImagePlugin.py | 2 +- Scripts/explode.py | 2 +- Scripts/gifmaker.py | 2 +- Scripts/pildriver.py | 6 +++--- Scripts/pilfile.py | 2 +- Tests/run.py | 4 ++-- Tests/test_image_transform.py | 4 ++-- Tests/test_imagesequence.py | 2 +- 35 files changed, 69 insertions(+), 75 deletions(-) diff --git a/PIL/ArgImagePlugin.py b/PIL/ArgImagePlugin.py index df5ff2625..7fc167c60 100644 --- a/PIL/ArgImagePlugin.py +++ b/PIL/ArgImagePlugin.py @@ -370,7 +370,7 @@ class ArgStream(ChunkStream): im1 = im1.chop_add_modulo(im0.crop(bbox)) im0.paste(im1, bbox) - self.count = self.count - 1 + self.count -= 1 if self.count == 0 and self.show: self.im = self.images[self.id] diff --git a/PIL/BdfFontFile.py b/PIL/BdfFontFile.py index 3be80d602..3a41848d8 100644 --- a/PIL/BdfFontFile.py +++ b/PIL/BdfFontFile.py @@ -128,5 +128,5 @@ class BdfFontFile(FontFile.FontFile): if not c: break id, ch, (xy, dst, src), im = c - if ch >= 0 and ch < len(self.glyph): + if 0 <= ch < len(self.glyph): self.glyph[ch] = xy, dst, src, im diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index 4d19c1f20..5e44e6fe5 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -93,7 +93,7 @@ def Ghostscript(tile, size, fp, scale=1): s = fp.read(100*1024) if not s: break - length = length - len(s) + length -= len(s) f.write(s) # Build ghostscript command @@ -148,7 +148,7 @@ class PSFile: def tell(self): pos = self.fp.tell() if self.char: - pos = pos - 1 + pos -= 1 return pos def readline(self): s = b"" diff --git a/PIL/ExifTags.py b/PIL/ExifTags.py index 16473f930..25cd08068 100644 --- a/PIL/ExifTags.py +++ b/PIL/ExifTags.py @@ -63,15 +63,12 @@ TAGS = { 0x0201: "JpegIFOffset", 0x0202: "JpegIFByteCount", 0x0211: "YCbCrCoefficients", - 0x0211: "YCbCrCoefficients", 0x0212: "YCbCrSubSampling", 0x0213: "YCbCrPositioning", - 0x0213: "YCbCrPositioning", - 0x0214: "ReferenceBlackWhite", 0x0214: "ReferenceBlackWhite", 0x1000: "RelatedImageFileFormat", - 0x1001: "RelatedImageLength", - 0x1001: "RelatedImageWidth", + 0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys + 0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys 0x828d: "CFARepeatPatternDim", 0x828e: "CFAPattern", 0x828f: "BatteryLevel", diff --git a/PIL/FliImagePlugin.py b/PIL/FliImagePlugin.py index bef9b722e..c9a29051e 100644 --- a/PIL/FliImagePlugin.py +++ b/PIL/FliImagePlugin.py @@ -105,7 +105,7 @@ class FliImageFile(ImageFile.ImageFile): g = i8(s[n+1]) << shift b = i8(s[n+2]) << shift palette[i] = (r, g, b) - i = i + 1 + i += 1 def seek(self, frame): diff --git a/PIL/FontFile.py b/PIL/FontFile.py index 4dce0a292..7c5704c9d 100644 --- a/PIL/FontFile.py +++ b/PIL/FontFile.py @@ -30,7 +30,7 @@ def puti16(fp, values): # write network order (big-endian) 16-bit sequence for v in values: if v < 0: - v = v + 65536 + v += 65536 fp.write(_binary.o16be(v)) ## @@ -63,7 +63,7 @@ class FontFile: h = max(h, src[3] - src[1]) w = w + (src[2] - src[0]) if w > WIDTH: - lines = lines + 1 + lines += 1 w = (src[2] - src[0]) maxwidth = max(maxwidth, w) diff --git a/PIL/FpxImagePlugin.py b/PIL/FpxImagePlugin.py index b712557d7..64c7b1568 100644 --- a/PIL/FpxImagePlugin.py +++ b/PIL/FpxImagePlugin.py @@ -84,7 +84,7 @@ class FpxImageFile(ImageFile.ImageFile): i = 1 while size > 64: size = size / 2 - i = i + 1 + i += 1 self.maxid = i - 1 # mode. instead of using a single field for this, flashpix diff --git a/PIL/GimpGradientFile.py b/PIL/GimpGradientFile.py index 4ae727773..7c88addae 100644 --- a/PIL/GimpGradientFile.py +++ b/PIL/GimpGradientFile.py @@ -68,7 +68,7 @@ class GradientFile: x = i / float(entries-1) while x1 < x: - ix = ix + 1 + ix += 1 x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix] w = x1 - x0 diff --git a/PIL/GimpPaletteFile.py b/PIL/GimpPaletteFile.py index c271b964f..6f71ec678 100644 --- a/PIL/GimpPaletteFile.py +++ b/PIL/GimpPaletteFile.py @@ -52,7 +52,7 @@ class GimpPaletteFile: if 0 <= i <= 255: self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) - i = i + 1 + i += 1 self.palette = b"".join(self.palette) diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py index 9a0864bad..6951c9325 100644 --- a/PIL/IcnsImagePlugin.py +++ b/PIL/IcnsImagePlugin.py @@ -70,7 +70,7 @@ def read_32(fobj, start_length, size): else: blocksize = byte + 1 data.append(fobj.read(blocksize)) - bytesleft = bytesleft - blocksize + bytesleft -= blocksize if bytesleft <= 0: break if bytesleft != 0: @@ -179,11 +179,11 @@ class IcnsFile: i = HEADERSIZE while i < filesize: sig, blocksize = nextheader(fobj) - i = i + HEADERSIZE - blocksize = blocksize - HEADERSIZE + i += HEADERSIZE + blocksize -= HEADERSIZE dct[sig] = (i, blocksize) fobj.seek(blocksize, 1) - i = i + blocksize + i += blocksize def itersizes(self): sizes = [] diff --git a/PIL/ImImagePlugin.py b/PIL/ImImagePlugin.py index 1f8f011dc..a5eeef76a 100644 --- a/PIL/ImImagePlugin.py +++ b/PIL/ImImagePlugin.py @@ -182,7 +182,7 @@ class ImImageFile(ImageFile.ImageFile): self.info[k] = v if k in TAGS: - n = n + 1 + n += 1 else: diff --git a/PIL/Image.py b/PIL/Image.py index 333397701..e481e664a 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -434,9 +434,9 @@ def _getscaleoffset(expr): data = expr(_E(stub)).data try: (a, b, c) = data # simplified syntax - if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)): + if a is stub and b == "__mul__" and isinstance(c, numbers.Number): return c, 0.0 - if (a is stub and b == "__add__" and isinstance(c, numbers.Number)): + if a is stub and b == "__add__" and isinstance(c, numbers.Number): return 1.0, c except TypeError: pass try: @@ -2002,7 +2002,6 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): .. versionadded:: 1.1.4 """ - "Load image from bytes or buffer" # may pass tuple instead of argument list if len(args) == 1 and isinstance(args[0], tuple): diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index 501e16b00..adb27f2bd 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -502,5 +502,5 @@ def _safe_read(fp, size): if not block: break data.append(block) - size = size - len(block) + size -= len(block) return b"".join(data) diff --git a/PIL/ImageMath.py b/PIL/ImageMath.py index b2355ed1d..adfcc4f6f 100644 --- a/PIL/ImageMath.py +++ b/PIL/ImageMath.py @@ -17,7 +17,6 @@ from PIL import Image from PIL import _imagingmath -import sys try: import builtins diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 0d22f8c64..64c35cca1 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -94,7 +94,7 @@ def autocontrast(image, cutoff=0, ignore=None): cut = cut - h[lo] h[lo] = 0 else: - h[lo] = h[lo] - cut + h[lo] -= cut cut = 0 if cut <= 0: break @@ -105,7 +105,7 @@ def autocontrast(image, cutoff=0, ignore=None): cut = cut - h[hi] h[hi] = 0 else: - h[hi] = h[hi] - cut + h[hi] -= cut cut = 0 if cut <= 0: break diff --git a/PIL/ImageShow.py b/PIL/ImageShow.py index e81866bac..40fe629d9 100644 --- a/PIL/ImageShow.py +++ b/PIL/ImageShow.py @@ -17,7 +17,7 @@ from __future__ import print_function from PIL import Image import os, sys -if(sys.version_info >= (3, 3)): +if sys.version_info >= (3, 3): from shlex import quote else: from pipes import quote @@ -160,7 +160,7 @@ else: # imagemagick's display command instead. command = executable = "xv" if title: - command = command + " -name %s" % quote(title) + command += " -name %s" % quote(title) return command, executable if which("xv"): diff --git a/PIL/ImageStat.py b/PIL/ImageStat.py index ef63b7857..d84e2cbf1 100644 --- a/PIL/ImageStat.py +++ b/PIL/ImageStat.py @@ -21,7 +21,6 @@ # See the README file for information on usage and redistribution. # -from PIL import Image import operator, math from functools import reduce @@ -81,7 +80,7 @@ class Stat: for i in range(0, len(self.h), 256): sum = 0.0 for j in range(256): - sum = sum + j * self.h[i+j] + sum += j * self.h[i + j] v.append(sum) return v @@ -92,7 +91,7 @@ class Stat: for i in range(0, len(self.h), 256): sum2 = 0.0 for j in range(256): - sum2 = sum2 + (j ** 2) * float(self.h[i+j]) + sum2 += (j ** 2) * float(self.h[i + j]) v.append(sum2) return v diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 104153002..85575612c 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -103,7 +103,7 @@ class IptcImageFile(ImageFile.ImageFile): break if s != sz: return 0 - y = y + 1 + y += 1 return y == size[1] def _open(self): @@ -187,7 +187,7 @@ class IptcImageFile(ImageFile.ImageFile): if not s: break o.write(s) - size = size - len(s) + size -= len(s) o.close() try: @@ -235,26 +235,26 @@ def getiptcinfo(im): # parse the image resource block offset = 0 while app[offset:offset+4] == "8BIM": - offset = offset + 4 + offset += 4 # resource code code = JpegImagePlugin.i16(app, offset) - offset = offset + 2 + offset += 2 # resource name (usually empty) name_len = i8(app[offset]) name = app[offset+1:offset+1+name_len] offset = 1 + offset + name_len if offset & 1: - offset = offset + 1 + offset += 1 # resource data block size = JpegImagePlugin.i32(app, offset) - offset = offset + 4 + offset += 4 if code == 0x0404: # 0x0404 contains IPTC/NAA data data = app[offset:offset+size] break offset = offset + size if offset & 1: - offset = offset + 1 + offset += 1 except (AttributeError, KeyError): pass diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 128248512..632aff0e5 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -15,7 +15,7 @@ __version__ = "0.1" -from PIL import Image, ImageFile, _binary +from PIL import Image, ImageFile import struct import os import io diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index da52006ca..39fb8fe9e 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -543,8 +543,8 @@ def _save(im, fp, filename): i = 1 for marker in markers: size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) - extra = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) - i = i + 1 + extra += b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker + i += 1 # get keyword arguments im.encoderconfig = ( diff --git a/PIL/MpegImagePlugin.py b/PIL/MpegImagePlugin.py index 9d7a0ea7a..02e6adc00 100644 --- a/PIL/MpegImagePlugin.py +++ b/PIL/MpegImagePlugin.py @@ -38,13 +38,13 @@ class BitStream: self.bits = 0 continue self.bitbuffer = (self.bitbuffer << 8) + c - self.bits = self.bits + 8 + self.bits += 8 return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1 def skip(self, bits): while self.bits < bits: self.bitbuffer = (self.bitbuffer << 8) + i8(self.fp.read(1)) - self.bits = self.bits + 8 + self.bits += 8 self.bits = self.bits - bits def read(self, bits): diff --git a/PIL/PalmImagePlugin.py b/PIL/PalmImagePlugin.py index 89f42bffc..203a6d9f6 100644 --- a/PIL/PalmImagePlugin.py +++ b/PIL/PalmImagePlugin.py @@ -172,21 +172,21 @@ def _save(im, fp, filename, check=0): cols = im.size[0] rows = im.size[1] - rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2; + rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2 transparent_index = 0 compression_type = _COMPRESSION_TYPES["none"] - flags = 0; + flags = 0 if im.mode == "P" and "custom-colormap" in im.info: flags = flags & _FLAGS["custom-colormap"] - colormapsize = 4 * 256 + 2; + colormapsize = 4 * 256 + 2 colormapmode = im.palette.mode colormap = im.getdata().getpalette() else: colormapsize = 0 if "offset" in im.info: - offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4; + offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4 else: offset = 0 diff --git a/PIL/PcxImagePlugin.py b/PIL/PcxImagePlugin.py index 2496af676..4f6d5a3e5 100644 --- a/PIL/PcxImagePlugin.py +++ b/PIL/PcxImagePlugin.py @@ -135,7 +135,7 @@ def _save(im, fp, filename, check=0): # bytes per plane stride = (im.size[0] * bits + 7) // 8 # stride should be even - stride = stride + (stride % 2) + stride += stride % 2 # Stride needs to be kept in sync with the PcxEncode.c version. # Ideally it should be passed in in the state, but the bytes value # gets overwritten. diff --git a/PIL/PdfImagePlugin.py b/PIL/PdfImagePlugin.py index 725f22ecf..f7f98d99b 100644 --- a/PIL/PdfImagePlugin.py +++ b/PIL/PdfImagePlugin.py @@ -104,8 +104,8 @@ def _save(im, fp, filename): r = i8(palette[i*3]) g = i8(palette[i*3+1]) b = i8(palette[i*3+2]) - colorspace = colorspace + "%02x%02x%02x " % (r, g, b) - colorspace = colorspace + b"> ]" + colorspace += "%02x%02x%02x " % (r, g, b) + colorspace += b"> ]" procset = "/ImageI" # indexed color elif im.mode == "RGB": filter = "/DCTDecode" diff --git a/PIL/PsdImagePlugin.py b/PIL/PsdImagePlugin.py index f6aefe9c9..9e64e7c90 100644 --- a/PIL/PsdImagePlugin.py +++ b/PIL/PsdImagePlugin.py @@ -235,7 +235,7 @@ def _layerinfo(file): if t: tile.extend(t) layers[i] = name, mode, bbox, tile - i = i + 1 + i += 1 return layers @@ -258,7 +258,7 @@ def _maketile(file, mode, bbox, channels): for channel in range(channels): layer = mode[channel] if mode == "CMYK": - layer = layer + ";I" + layer += ";I" tile.append(("raw", bbox, offset, layer)) offset = offset + xsize*ysize @@ -272,13 +272,13 @@ def _maketile(file, mode, bbox, channels): for channel in range(channels): layer = mode[channel] if mode == "CMYK": - layer = layer + ";I" + layer += ";I" tile.append( ("packbits", bbox, offset, layer) ) for y in range(ysize): offset = offset + i16(bytecount[i:i+2]) - i = i + 2 + i += 2 file.seek(offset) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index fe658d22c..2d6cbf7dc 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -558,9 +558,9 @@ class ImageFileDirectory(collections.MutableMapping): count = count // 2 # adjust for rational data field append((tag, typ, count, o32(offset), data)) - offset = offset + len(data) + offset += len(data) if offset & 1: - offset = offset + 1 # word padding + offset += 1 # word padding # update strip offset data to point beyond auxiliary data if stripoffsets is not None: @@ -644,7 +644,7 @@ class TiffImageFile(ImageFile.ImageFile): self.fp.seek(self.__next) self.tag.load(self.fp) self.__next = self.tag.next - self.__frame = self.__frame + 1 + self.__frame += 1 self._setup() def _tell(self): @@ -900,7 +900,7 @@ class TiffImageFile(ImageFile.ImageFile): y = y + h if y >= self.size[1]: x = y = 0 - l = l + 1 + l += 1 a = None elif TILEOFFSETS in self.tag: # tiled image @@ -921,7 +921,7 @@ class TiffImageFile(ImageFile.ImageFile): x, y = 0, y + h if y >= self.size[1]: x = y = 0 - l = l + 1 + l += 1 a = None else: if Image.DEBUG: @@ -1093,7 +1093,7 @@ def _save(im, fp, filename): fp.seek(0) _fp = os.dup(fp.fileno()) - blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. + blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. atts={} # bits per sample is a single short in the tiff directory, not a list. atts[BITSPERSAMPLE] = bits[0] diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index 9d4530051..92a4b5afc 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -161,7 +161,7 @@ TAGS = { 50716: "BlackLevelDeltaV", 50717: "WhiteLevel", 50718: "DefaultScale", - 50741: "BestQualityScale", + 50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741 50719: "DefaultCropOrigin", 50720: "DefaultCropSize", 50778: "CalibrationIlluminant1", @@ -185,7 +185,7 @@ TAGS = { 50737: "ChromaBlurRadius", 50738: "AntiAliasStrength", 50740: "DNGPrivateData", - 50741: "MakerNoteSafety", + 50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741 #ImageJ 50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe diff --git a/PIL/WmfImagePlugin.py b/PIL/WmfImagePlugin.py index 9a95a0713..40b2037ab 100644 --- a/PIL/WmfImagePlugin.py +++ b/PIL/WmfImagePlugin.py @@ -59,7 +59,7 @@ word = _binary.i16le def short(c, o=0): v = word(c, o) if v >= 32768: - v = v - 65536 + v -= 65536 return v dword = _binary.i32le diff --git a/Scripts/explode.py b/Scripts/explode.py index 90084a464..b8680f631 100644 --- a/Scripts/explode.py +++ b/Scripts/explode.py @@ -104,7 +104,7 @@ while True: except EOFError: break - ix = ix + 1 + ix += 1 if html: html.write("\n\n") diff --git a/Scripts/gifmaker.py b/Scripts/gifmaker.py index 9964f77b1..9fa5e71a4 100644 --- a/Scripts/gifmaker.py +++ b/Scripts/gifmaker.py @@ -100,7 +100,7 @@ def makedelta(fp, sequence): previous = im.copy() - frames = frames + 1 + frames += 1 fp.write(";") diff --git a/Scripts/pildriver.py b/Scripts/pildriver.py index 98708c897..e45b05008 100644 --- a/Scripts/pildriver.py +++ b/Scripts/pildriver.py @@ -486,7 +486,7 @@ class PILDriver: print("Stack: " + repr(self.stack)) top = self.top() if not isinstance(top, str): - continue; + continue funcname = "do_" + top if not hasattr(self, funcname): continue @@ -513,9 +513,9 @@ if __name__ == '__main__': while True: try: if sys.version_info[0] >= 3: - line = input('pildriver> '); + line = input('pildriver> ') else: - line = raw_input('pildriver> '); + line = raw_input('pildriver> ') except EOFError: print("\nPILDriver says goodbye.") break diff --git a/Scripts/pilfile.py b/Scripts/pilfile.py index 48514e88b..1b77b0e78 100644 --- a/Scripts/pilfile.py +++ b/Scripts/pilfile.py @@ -57,7 +57,7 @@ for o, a in opt: elif o == "-v": verify = 1 elif o == "-D": - Image.DEBUG = Image.DEBUG + 1 + Image.DEBUG += 1 def globfix(files): # expand wildcards where necessary diff --git a/Tests/run.py b/Tests/run.py index 758923f0f..82e1b94dc 100644 --- a/Tests/run.py +++ b/Tests/run.py @@ -72,7 +72,7 @@ for file in files: if not p.startswith('^'): p = '^' + p if not p.endswith('$'): - p = p + '$' + p += '$' return p ignore_res = [re.compile(fix_re(p), re.MULTILINE) for p in ignore_pats] @@ -104,7 +104,7 @@ for file in files: print(result) failed.append(test) else: - success = success + 1 + success += 1 print("-"*68) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index fdee6072f..2176d2ea1 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -61,8 +61,8 @@ def _test_alpha_premult(op): # create image with half white, half black, with the black half transparent. # do op, # there should be no darkness in the white section. - im = Image.new('RGBA', (10,10), (0,0,0,0)); - im2 = Image.new('RGBA', (5,10), (255,255,255,255)); + im = Image.new('RGBA', (10,10), (0,0,0,0)) + im2 = Image.new('RGBA', (5,10), (255,255,255,255)) im.paste(im2, (0,0)) im = op(im, (40,10)) diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 3329b1a05..bf98f767d 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -16,7 +16,7 @@ def test_sanity(): for frame in seq: assert_image_equal(im, frame) assert_equal(im.tell(), index) - index = index + 1 + index += 1 assert_equal(index, 1) From 92eea7a9cc5022750e82ba07da96612bdea03f72 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 May 2014 17:32:04 +0300 Subject: [PATCH 024/488] Tests for ImageDraw --- Tests/images/imagedraw_arc.png | Bin 0 -> 284 bytes Tests/images/imagedraw_chord.png | Bin 0 -> 326 bytes Tests/images/imagedraw_ellipse.png | Bin 0 -> 491 bytes Tests/images/imagedraw_pieslice.png | Bin 0 -> 405 bytes Tests/test_imagedraw.py | 106 ++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 Tests/images/imagedraw_arc.png create mode 100644 Tests/images/imagedraw_chord.png create mode 100644 Tests/images/imagedraw_ellipse.png create mode 100644 Tests/images/imagedraw_pieslice.png diff --git a/Tests/images/imagedraw_arc.png b/Tests/images/imagedraw_arc.png new file mode 100644 index 0000000000000000000000000000000000000000..b097743890cb1907122e53a98231e8887d791d12 GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^DIm3c2+)C>xpS`>3f5(Zs9FD75X*zkc7DBDk+omSCdxl;y1HgSsJzU zmEpZza$2#*Ruz7e8#YW!Ufg!%%NmQF_u8+!e@Xt)YcH*9vF6qLSDa@!ATD08i}~^A Vp2dg61W$kjJYD@<);T3K0RX^ebT$A0 literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_chord.png b/Tests/images/imagedraw_chord.png new file mode 100644 index 0000000000000000000000000000000000000000..db3b35310235b1c7ab6e368d08f3bf89b9836777 GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^DImUz33VYkl1=nX|dqBVT;otL=PqD@Cub z;$2<(Q7d|t+$rnTn(Kcp*>>*yf#>4CDh^JY@$Oe#xmVO0!f&cvEFGQ N0Z&&ymvv4FO#l*njdTD2 literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_ellipse.png b/Tests/images/imagedraw_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..fb03fd148597a95bd4c446243b064e91561973ff GIT binary patch literal 491 zcmeAS@N?(olHy`uVBq!ia0vp^DImVh<$#e#iZH>9#NfrDIbrR>YpU ze7$YWlI^<={o3`zoN@0bj&rel?AHhzp0!dkZr|!Ur!TXt|4fOf{JhIYI({vYbH8n~ zc*b*c!6RReTsp#|>pefj$JfVGcADzSxpw-g5y}5e?m2%e=DqrGY51nd$yPnSC9%7v zEtR#4I=7B9`^|jce)dfVy-RF;Wee0^^mI+TdYdu*=+ur~iRGIwT?vrAreSw4a`C5x zryunVqRx7`>s@dDGT$MBb8F(-V);2!LqqKjtT#z;Ewn8;%yP&}VGRnQF@K7lS*QNm zSdk>^3}sWN-MWEb#G4)TthcJgG5d9|o^KVGt;09S8ivU?I=I@PlRl V%h(%|l_|GCVxF#kF6*2UngEBh-Om63 literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png new file mode 100644 index 0000000000000000000000000000000000000000..1b2acff92ab9aa263ce85ceb1a37493d4cbef6cb GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^DImmi_QNfYxaH53Vj{XAGUgN#YVIFC)qlsvNwOXirup8(c8%OTW-&O7bl%x zb9aX7OqFSEUrq^oubqDX-&Cz>|MDXD^3Br?du+5*=-8*|9?s;GxrWbru1ff?+p^I{ zuQM|_H>Z5vnT=<_gy8GO{EpOZxvKrC)xJJ*r^PnPDO_VdQk8HsVzb8!n${V^hH)%9N+zbw06{%>NP@%}SSuANIouO%;tm~~G(Ovm Date: Mon, 12 May 2014 19:55:18 +0300 Subject: [PATCH 025/488] Make sure bounding box in ints (for Py3) --- Tests/test_imagedraw.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 11f4fcd91..9904457af 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -7,10 +7,10 @@ from PIL import ImageDraw w, h = 100, 100 # Bounding box points -x0 = w / 4 -x1 = x0 * 3 -y0 = h / 4 -y1 = x0 * 3 +x0 = int(w / 4) +x1 = int(x0 * 3) +y0 = int(h / 4) +y1 = int(x0 * 3) # Two kinds of bounding box bbox1 = [(x0, y0), (x1, y1)] From 6b274b4c5e22a06ad72a682f7cb80a8b541dd87b Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 May 2014 21:45:16 +0300 Subject: [PATCH 026/488] More ImageDraw tests. Some may need redoing after issues #367 and #463 are sorted. --- Tests/images/imagedraw_line.png | Bin 0 -> 286 bytes Tests/images/imagedraw_point.png | Bin 0 -> 124 bytes Tests/images/imagedraw_polygon.png | Bin 0 -> 292 bytes Tests/images/imagedraw_rectangle.png | Bin 0 -> 228 bytes Tests/test_imagedraw.py | 88 +++++++++++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 Tests/images/imagedraw_line.png create mode 100644 Tests/images/imagedraw_point.png create mode 100644 Tests/images/imagedraw_polygon.png create mode 100644 Tests/images/imagedraw_rectangle.png diff --git a/Tests/images/imagedraw_line.png b/Tests/images/imagedraw_line.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0e6994d861246c94a905d395b683775489ed09 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^DIm(?a5DGbZ$zeim|1j!c@7cxq%qP7CDP~|u_;jC{;ef806_|%a9N^|HJI(rAL?`wsNYvBS&t;uc GLK6VpyB)Xy literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_polygon.png b/Tests/images/imagedraw_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..5f160be76ceef3d85c7092a63d87e48115ec3303 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^DImw+vGjmJT^EHnX^geT_FfuUkUHZNI{sZ}!Rk|0m4{9vEKINY9-e)hTs>plK zf0wzoVExtz&HLGxuB(RaJN`74*Yta-VVKvqS^Za|;!cOWF0GApUKv-OyfkS2(un&p zj{=rlZ%gGBt;+45%Ct8u`ENz%;hTHQ^s+5>T>lqgxjlUMmyq|u{M){)zW?9dcGH?U z5nB95tu}?MJNE0X`Nu0kH}|b+_D;=Tv)TLZmtXf^?!N!LuzFf(O0aa2sAg&W`hwcs f8mvISp@4!0|C_PVOkF`octGNwu6{1-oD!M-=- z>O8hT9}YG)8MEq~w&nw>0Rg?=IellJ-B`Bs_km6zUKttvYn|@ihKNQQ!h`vh`P05b c>}QaB%D Date: Mon, 12 May 2014 23:27:02 +0300 Subject: [PATCH 027/488] Test experimental floodfill() --- Tests/images/imagedraw_floodfill.png | Bin 0 -> 232 bytes Tests/test_imagedraw.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 Tests/images/imagedraw_floodfill.png diff --git a/Tests/images/imagedraw_floodfill.png b/Tests/images/imagedraw_floodfill.png new file mode 100644 index 0000000000000000000000000000000000000000..89376a0f01585431b6dfcdce1ab78d41ad1ae0c0 GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^DIm>$A6c=E^p z_-RJRvt}Gvu!@~0CVtOxMxZJnIFS6j;PtMyWbfxMrrtk$=Dg9r6Ysd~H!l?z(55Af ikWc^j+E5$nh9kTQ1uU8CSMGTP67Y2Ob6Mw<&;$VOggeUs literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 727275c2d..73617b97a 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,6 +1,7 @@ from tester import * from PIL import Image +from PIL import ImageColor from PIL import ImageDraw # Image size @@ -219,4 +220,19 @@ def test_rectangle2(): helper_rectangle(bbox2) +def test_floodfill(): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) + + # End of file From 5b55cb72d33c692f1677341311ec3df05dd0b500 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 May 2014 23:33:05 +0300 Subject: [PATCH 028/488] Test experimental floodfill with a border --- Tests/images/imagedraw_floodfill2.png | Bin 0 -> 212 bytes Tests/test_imagedraw.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 Tests/images/imagedraw_floodfill2.png diff --git a/Tests/images/imagedraw_floodfill2.png b/Tests/images/imagedraw_floodfill2.png new file mode 100644 index 0000000000000000000000000000000000000000..41b92fb75a032515147d1d63d27f45d61d85f69b GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^DIm?LEc!(!8PgVbG9L%=Jzf1= J);T3K0RX!!He~<+ literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 73617b97a..2a1f963c7 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -235,4 +235,21 @@ def test_floodfill(): assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) +def test_floodfill_border(): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill( + im, centre_point, ImageColor.getrgb("red"), + border=ImageColor.getrgb("black")) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + + # End of file From 60b25701332bb2056e3903761f456849186f046f Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 13 May 2014 14:43:48 +0300 Subject: [PATCH 029/488] Test ImageDraw's bitmap() --- Tests/images/imagedraw_bitmap.png | Bin 0 -> 2127 bytes Tests/test_imagedraw.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 Tests/images/imagedraw_bitmap.png diff --git a/Tests/images/imagedraw_bitmap.png b/Tests/images/imagedraw_bitmap.png new file mode 100644 index 0000000000000000000000000000000000000000..05337b693928aa27ede94ddf8aa1b4afc534ff8c GIT binary patch literal 2127 zcmaKudr;Eb7RP^@CC6j=2+6DFBPgwlMoLPC*~KvN_(%>Tzgi>yS|CmJzIj7)3jly1xD)?4opSFZ%QtGL$W(A4S>o-O&f52I zJLT~to9+TKdYxQWVhSnOj}3*RMfSv zA}P78VqCOQF|G(jOiQIP6HZT*!n7Ts$w>}Jzb!e4(JHjY4GjB~4Or*qe}wM3JV7{j z4!!Vh(RB8a#Uxe!NxR$ZJA$uyH6?iG&kLV@O3CX7iT_l({La1S0dww9_2lSHzfhrR zr_ko@aACYzUcB_=h!KCyK0VfRPF!8!UR&q^Guv_5On}hE3?Ottp`GW0z;%I5f6IQy zzm%*!YQ*0^dhm{O^3t2-DM7%)(wu*iuY zgz3Mpt*t4gt-;bKl(XPI|A(iWM!Y0zPS?!0orDJ3m9mxG)>Tj<6q>$ry*+K?c@j8IPmnb7EPLe0KxPbdlZ{=UhV7T<6uV zoKAA9&6R|NBI8|{0WRELIA9&`)e?|NCSGUG9yKYRqqYp~bg*iG3yTPY@%Kn+>wSz} z`Y#hY;6iSFv_1yJZfq1qO1N-o;%K;VN=4Lk&zB|XmFYGx76&tIX?JmOxO~IO6^(AT(V@Mr4rt!#Z20$b zFJAMcq`|kq+{O7tu}>8mHyd=UT8b3qFy6+^+fEXkNM=m-ZVJekMQ)A;?_u3-ZjEd?99&~)b2uB&GpKkWrH$|-?6C2fG zqPq=RIFN7Zgy>hB!jUTVUmmx7EtN_Y3JcwBztjycEW|ig;PE4R>~Vu!tm6&l)a~%R zWp!Q}K!JrTk0Mh&KW;fmt>P$eVyr4ek!?h*-atKrQc_Zyy>@pY0n$H)$UA3OCZ3CZa;9xOuP-bsE6dHz zEz@#mmL#3Yn^MTUjdBp#rLb)1urjYY-5;BYvK38laP@%2;J z_4Na|qA66SrPizz9na<2)VcW)87eC&?zzjUs|n)rBmMc9`Q4Gpzj5NiU%^DD!{ z!{?o#G)r2EXWf|=4CqilpW_B16ZhT3%6PZ~BYHlam0PkA>TAhla!|Nq#a^l(@IsBP zi@2h?TG6DvvgQ4!#9)P7{%THD=RepkTg=MEW>vpMq?}-AL)BbMYpZMe#`DA& z84rdl@thefwhjmgVAZ>%d=B{vB?ju#QqXoFO+oy}GDQD<`&lvl%E_?8KYQOkOSU+c z7RA~pRsn$F`2%!yZyBK6ms<-H?8?rPNL`obnCc3I1$QLOY?=>pGcE7?Ka|Vm$3xmB zFkzkIxe*)s*keRFFKIBI8XqSDKs=IFKajmdN#?rSHfv~J4NX?#+`=CRM z^|Um*LmsuaJDKFKuB;GB6Yq9Cj$MS>U_txo{x)Dl|47V8;@b%OTZfW@r7E;xj2dvy zIt;!3e&L-;4R!0J*F5d$DEF+>%mBuF5xOdi6}6vv@wG!X5C%s%s!XKa<|xes^L(GQ z(kaxJRF`UT@zKEvUl^1mMYa&&?zMiwQd{AHGGSxmcf^S?#(F2dYVK-kDxyE)B&NpG zFn0@Joc)e^%)DR=Fwm8oL{Xxcu_ck-l0T5%czm#Q6VkbA4Jkj|HTK3{*A3IqnUrKE zSVmibv<5cw)1GRq>`pJNuKC}{{qN-cKawNf1ahkTB$z@01oSOf`bXk{5P{v B0-gW> literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 2a1f963c7..0a9366928 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -71,6 +71,20 @@ def test_arc2(): helper_arc(bbox2) +def test_bitmap(): + # Arrange + small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.bitmap((10, 10), small) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) + + def helper_chord(bbox): # Arrange im = Image.new("RGB", (w, h)) From ce2955ec71284d12259b16fc7cf472ab2d16f83b Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 14 May 2014 18:04:18 +0300 Subject: [PATCH 030/488] Throw an exception when an opened image is larger than an arbitrary limit --- PIL/Image.py | 68 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 333397701..e33454d90 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -35,6 +35,12 @@ class _imaging_not_installed: def __getattr__(self, id): raise ImportError("The _imaging C module is not installed") + +class ImageIsTooBigError(Exception): + pass + +ARBITARY_LARGE_LIMIT = 6000 * 6000 - 1 # FIXME: Pick sensible limit + try: # give Tk a chance to set up the environment, in case we're # using an _imaging module linked against libtcl/libtk (use @@ -101,7 +107,7 @@ import collections import numbers # works everywhere, win for pypy, not cpython -USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') +USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') try: import cffi HAS_CFFI=True @@ -233,7 +239,7 @@ _MODE_CONV = { "CMYK": ('|u1', 4), "YCbCr": ('|u1', 3), "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1 - # I;16 == I;16L, and I;32 == I;32L + # I;16 == I;16L, and I;32 == I;32L "I;16": ('u2', None), "I;16L": (' 8bit images. + # a gamma function point transform on > 8bit images. scale, offset = _getscaleoffset(lut) return self._new(self.im.point_transform(scale, offset)) # for other modes, convert the function to a table @@ -1420,8 +1426,8 @@ class Image: self._copy() self.pyaccess = None self.load() - - if self.pyaccess: + + if self.pyaccess: return self.pyaccess.putpixel(xy,value) return self.im.putpixel(xy, value) @@ -2100,7 +2106,18 @@ _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I") _fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F") -def open(fp, mode="r"): +def _compression_bomb_check(im, maximum_pixels): + if maximum_pixels is None: + return + + pixels = im.size[0] * im.size[1] + print("Pixels:", pixels) # FIXME: temporary + + if im.size[0] * im.size[1] > maximum_pixels: + raise ImageIsTooBigError("Image size exceeds limit") + + +def open(fp, mode="r", maximum_pixels=ARBITARY_LARGE_LIMIT): """ Opens and identifies the given image file. @@ -2114,6 +2131,7 @@ def open(fp, mode="r"): must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and :py:meth:`~file.tell` methods, and be opened in binary mode. :param mode: The mode. If given, this argument must be "r". + :param maximum_pixels: TODO. :returns: An :py:class:`~PIL.Image.Image` object. :exception IOError: If the file cannot be found, or the image cannot be opened and identified. @@ -2137,7 +2155,10 @@ def open(fp, mode="r"): factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) - return factory(fp, filename) + # return factory(fp, filename) + im = factory(fp, filename) + _compression_bomb_check(im, maximum_pixels) + return im except (SyntaxError, IndexError, TypeError): #import traceback #traceback.print_exc() @@ -2150,7 +2171,10 @@ def open(fp, mode="r"): factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) - return factory(fp, filename) + # return factory(fp, filename) + im = factory(fp, filename) + _compression_bomb_check(im, maximum_pixels) + return im except (SyntaxError, IndexError, TypeError): #import traceback #traceback.print_exc() From 2662c38f5c5b7b4da8007f51c6020766d9e3f63f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 20 May 2014 12:59:31 -0700 Subject: [PATCH 031/488] Updating docs to reflect current understanding --- docs/reference/ImageDraw.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 68855eb5b..45be43057 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -91,9 +91,12 @@ Methods Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. - :param xy: Four points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. - :param outline: Color to use for the outline. + :param xy: Four points to define the bounding box. Sequence of + ``[x0, y0, x1, y1]``. + :param start: Starting angle, in degrees. Angles are measured from + 3 o'clock, increasing clockwise. + :param end: Ending angle, in degrees. + :param fill: Color to use for the arc. .. py:method:: PIL.ImageDraw.Draw.bitmap(xy, bitmap, fill=None) @@ -111,8 +114,8 @@ Methods Same as :py:meth:`~PIL.ImageDraw.Draw.arc`, but connects the end points with a straight line. - :param xy: Four points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + :param xy: Four points to define the bounding box. Sequence of + ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. @@ -144,8 +147,8 @@ Methods Same as arc, but also draws straight lines between the end points and the center of the bounding box. - :param xy: Four points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + :param xy: Four points to define the bounding box. Sequence of + ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. From 35336e5afd5e3eb9ea2991eaf3b705877c49a173 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 May 2014 17:31:37 +0300 Subject: [PATCH 032/488] Test arc, chord, pieslice give TypeError when bounding box is [(x0, y0), (x1, y1)] --- Tests/test_imagedraw.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 0a9366928..21eca5038 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -62,9 +62,8 @@ def helper_arc(bbox): assert_image_equal(im, Image.open("Tests/images/imagedraw_arc.png")) -# FIXME -# def test_arc1(): -# helper_arc(bbox1) +def test_arc1(): + assert_exception(TypeError, lambda: helper_arc(bbox1)) def test_arc2(): @@ -98,9 +97,8 @@ def helper_chord(bbox): assert_image_equal(im, Image.open("Tests/images/imagedraw_chord.png")) -# FIXME -# def test_chord1(): -# helper_chord(bbox1) +def test_chord1(): + assert_exception(TypeError, lambda: helper_chord(bbox1)) def test_chord2(): @@ -162,9 +160,8 @@ def helper_pieslice(bbox): assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) -# FIXME -# def test_pieslice1(): -# helper_pieslice(bbox1) +def test_pieslice1(): + assert_exception(TypeError, lambda: helper_pieslice(bbox1)) def test_pieslice2(): From 8f92562ec353e7a2efeb96fb85aa5d76f3270492 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 May 2014 17:36:31 +0300 Subject: [PATCH 033/488] Remove old FIXME comment [CI skip] --- Tests/test_imagedraw.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 21eca5038..2e0cc07c0 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -53,7 +53,6 @@ def helper_arc(bbox): draw = ImageDraw.Draw(im) # Act - # FIXME Should docs note 0 degrees is at 3 o'clock? # FIXME Fill param should be named outline. draw.arc(bbox, 0, 180) del draw From b7e5c276964898f00e21329b032fdb7147b41cc4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 May 2014 17:52:48 +0300 Subject: [PATCH 034/488] Remove temporary print --- PIL/Image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index e33454d90..fac38640e 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2111,7 +2111,6 @@ def _compression_bomb_check(im, maximum_pixels): return pixels = im.size[0] * im.size[1] - print("Pixels:", pixels) # FIXME: temporary if im.size[0] * im.size[1] > maximum_pixels: raise ImageIsTooBigError("Image size exceeds limit") From 35f1f4d8fa9b290cfed877faef35336614a9c0eb Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 16:25:15 +0300 Subject: [PATCH 035/488] Change exception into a warning --- PIL/Image.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index fac38640e..6450458bc 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -36,10 +36,7 @@ class _imaging_not_installed: raise ImportError("The _imaging C module is not installed") -class ImageIsTooBigError(Exception): - pass - -ARBITARY_LARGE_LIMIT = 6000 * 6000 - 1 # FIXME: Pick sensible limit +MAX_IMAGE_PIXELS = 6000 * 6000 - 1 # FIXME: Pick sensible limit try: # give Tk a chance to set up the environment, in case we're @@ -2106,17 +2103,21 @@ _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I") _fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F") -def _compression_bomb_check(im, maximum_pixels): - if maximum_pixels is None: +def _compression_bomb_check(size): + if MAX_IMAGE_PIXELS is None: return - pixels = im.size[0] * im.size[1] + pixels = size[0] * size[1] - if im.size[0] * im.size[1] > maximum_pixels: - raise ImageIsTooBigError("Image size exceeds limit") + if pixels > MAX_IMAGE_PIXELS: + warnings.warn( + "Image size (%d pixels) exceeds limit of %d pixels, " + "could be decompression bomb DOS attack." % + (pixels, MAX_IMAGE_PIXELS), + RuntimeWarning) -def open(fp, mode="r", maximum_pixels=ARBITARY_LARGE_LIMIT): +def open(fp, mode="r"): """ Opens and identifies the given image file. @@ -2156,7 +2157,7 @@ def open(fp, mode="r", maximum_pixels=ARBITARY_LARGE_LIMIT): fp.seek(0) # return factory(fp, filename) im = factory(fp, filename) - _compression_bomb_check(im, maximum_pixels) + _compression_bomb_check(im.size) return im except (SyntaxError, IndexError, TypeError): #import traceback @@ -2172,7 +2173,7 @@ def open(fp, mode="r", maximum_pixels=ARBITARY_LARGE_LIMIT): fp.seek(0) # return factory(fp, filename) im = factory(fp, filename) - _compression_bomb_check(im, maximum_pixels) + _compression_bomb_check(im.size) return im except (SyntaxError, IndexError, TypeError): #import traceback From 29388f839582f35a541a7506c9818e68cf2bfb26 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 16:26:42 +0300 Subject: [PATCH 036/488] Remove redundant comment [CI skip] --- PIL/Image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 6450458bc..7014d36f3 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2131,7 +2131,6 @@ def open(fp, mode="r"): must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and :py:meth:`~file.tell` methods, and be opened in binary mode. :param mode: The mode. If given, this argument must be "r". - :param maximum_pixels: TODO. :returns: An :py:class:`~PIL.Image.Image` object. :exception IOError: If the file cannot be found, or the image cannot be opened and identified. From a0d8e5cb337de3dc921d54a90e0eb4b489da333e Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 27 May 2014 12:10:10 +0300 Subject: [PATCH 037/488] Set limit to to around a quarter gigabyte for a 24 bit (3 bpp) image --- PIL/Image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 7014d36f3..6dd9b2310 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -36,7 +36,8 @@ class _imaging_not_installed: raise ImportError("The _imaging C module is not installed") -MAX_IMAGE_PIXELS = 6000 * 6000 - 1 # FIXME: Pick sensible limit +# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image +MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 / 4 / 3) try: # give Tk a chance to set up the environment, in case we're @@ -2157,6 +2158,7 @@ def open(fp, mode="r"): # return factory(fp, filename) im = factory(fp, filename) _compression_bomb_check(im.size) + print(im) return im except (SyntaxError, IndexError, TypeError): #import traceback From b853696ad54169c5242e635ec08504233cd5e946 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 27 May 2014 12:18:56 +0300 Subject: [PATCH 038/488] Remove stray debug print --- PIL/Image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 6dd9b2310..f8977944b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2158,7 +2158,6 @@ def open(fp, mode="r"): # return factory(fp, filename) im = factory(fp, filename) _compression_bomb_check(im.size) - print(im) return im except (SyntaxError, IndexError, TypeError): #import traceback From fd05e9c7560b0feb44e4a6cfd6d44ad28b8a1019 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 27 May 2014 12:40:52 +0300 Subject: [PATCH 039/488] Test decompression bomb warnings --- PIL/Image.py | 6 +++--- Tests/test_decompression_bomb.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Tests/test_decompression_bomb.py diff --git a/PIL/Image.py b/PIL/Image.py index f8977944b..5ffa5b64b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2104,7 +2104,7 @@ _fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I") _fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F") -def _compression_bomb_check(size): +def _decompression_bomb_check(size): if MAX_IMAGE_PIXELS is None: return @@ -2157,7 +2157,7 @@ def open(fp, mode="r"): fp.seek(0) # return factory(fp, filename) im = factory(fp, filename) - _compression_bomb_check(im.size) + _decompression_bomb_check(im.size) return im except (SyntaxError, IndexError, TypeError): #import traceback @@ -2173,7 +2173,7 @@ def open(fp, mode="r"): fp.seek(0) # return factory(fp, filename) im = factory(fp, filename) - _compression_bomb_check(im.size) + _decompression_bomb_check(im.size) return im except (SyntaxError, IndexError, TypeError): #import traceback diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py new file mode 100644 index 000000000..1b3873c1b --- /dev/null +++ b/Tests/test_decompression_bomb.py @@ -0,0 +1,37 @@ +from tester import * + +from PIL import Image + +test_file = "Images/lena.ppm" + + +def test_no_warning_small_file(): + # Implicit assert: no warning. + # A warning would cause a failure. + Image.open(test_file) + + +def test_no_warning_no_limit(): + # Arrange + # Turn limit off + Image.MAX_IMAGE_PIXELS = None + assert_equal(Image.MAX_IMAGE_PIXELS, None) + + # Act / Assert + # Implicit assert: no warning. + # A warning would cause a failure. + Image.open(test_file) + + +def test_warning(): + # Arrange + # Set limit to a low, easily testable value + Image.MAX_IMAGE_PIXELS = 10 + assert_equal(Image.MAX_IMAGE_PIXELS, 10) + + # Act / Assert + assert_warning( + RuntimeWarning, + lambda: Image.open(test_file)) + +# End of file From d7ed249b29e4fab1c5aea41632cd5490cbf9a290 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 27 May 2014 14:39:33 +0300 Subject: [PATCH 040/488] Remove redundant commented code [CI skip] --- PIL/Image.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 5ffa5b64b..d1d5c510e 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2155,7 +2155,6 @@ def open(fp, mode="r"): factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) - # return factory(fp, filename) im = factory(fp, filename) _decompression_bomb_check(im.size) return im @@ -2171,7 +2170,6 @@ def open(fp, mode="r"): factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) - # return factory(fp, filename) im = factory(fp, filename) _decompression_bomb_check(im.size) return im From 1011e51083e475ddea26e6e15f4772a22d505ddb Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 27 May 2014 12:43:54 +0100 Subject: [PATCH 041/488] Added support for OpenJPEG 2.1. --- PIL/Jpeg2KImagePlugin.py | 16 ++++++++--- decode.c | 6 +++-- libImaging/Jpeg2K.h | 5 +++- libImaging/Jpeg2KDecode.c | 13 +++++++++ libImaging/Jpeg2KEncode.c | 4 +++ setup.py | 56 ++++++++++++++++++++++++++++++--------- 6 files changed, 81 insertions(+), 19 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index f57f4a784..31e6f0a13 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -155,15 +155,25 @@ class Jpeg2KImageFile(ImageFile.ImageFile): self.layers = 0 fd = -1 + length = -1 if hasattr(self.fp, "fileno"): try: fd = self.fp.fileno() + length = os.fstat(fd).st_size except: fd = -1 - + elif hasattr(self.fp, "seek"): + try: + pos = f.tell() + seek(0, 2) + length = f.tell() + seek(pos, 0) + except: + length = -1 + self.tile = [('jpeg2k', (0, 0) + self.size, 0, - (self.codec, self.reduce, self.layers, fd))] + (self.codec, self.reduce, self.layers, fd, length))] def load(self): if self.reduce: @@ -175,7 +185,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): if self.tile: # Update the reduce and layers settings t = self.tile[0] - t3 = (t[3][0], self.reduce, self.layers, t[3][3]) + t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4]) self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] ImageFile.ImageFile.load(self) diff --git a/decode.c b/decode.c index 33367dfe3..d5e329384 100644 --- a/decode.c +++ b/decode.c @@ -797,8 +797,9 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) int reduce = 0; int layers = 0; int fd = -1; - if (!PyArg_ParseTuple(args, "ss|iii", &mode, &format, - &reduce, &layers, &fd)) + PY_LONG_LONG length = -1; + if (!PyArg_ParseTuple(args, "ss|iiiL", &mode, &format, + &reduce, &layers, &fd, &length)) return NULL; if (strcmp(format, "j2k") == 0) @@ -821,6 +822,7 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) context = (JPEG2KDECODESTATE *)decoder->state.context; context->fd = fd; + context->length = (off_t)length; context->format = codec_format; context->reduce = reduce; context->layers = layers; diff --git a/libImaging/Jpeg2K.h b/libImaging/Jpeg2K.h index be6e0770b..fd53b59df 100644 --- a/libImaging/Jpeg2K.h +++ b/libImaging/Jpeg2K.h @@ -8,7 +8,7 @@ * Copyright (c) 2014 by Alastair Houghton */ -#include +#include /* -------------------------------------------------------------------- */ /* Decoder */ @@ -20,6 +20,9 @@ typedef struct { /* File descriptor, if available; otherwise, -1 */ int fd; + /* Length of data, if available; otherwise, -1 */ + off_t length; + /* Specify the desired format */ OPJ_CODEC_FORMAT format; diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 6b6176c78..43a796c27 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -517,7 +517,20 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, opj_stream_set_read_function(stream, j2k_read); opj_stream_set_skip_function(stream, j2k_skip); +#if OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 0 opj_stream_set_user_data(stream, decoder); +#else + opj_stream_set_user_data(stream, decoder, NULL); + + /* Hack: if we don't know the length, the largest file we can + possibly support is 4GB. We can't go larger than this, because + OpenJPEG truncates this value for the final box in the file, and + the box lengths in OpenJPEG are currently 32 bit. */ + if (context->length < 0) + opj_stream_set_user_data_length(stream, 0xffffffff); + else + opj_stream_set_user_data_length(stream, context->length); +#endif /* Setup decompression context */ context->error_msg = NULL; diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index c1e16e97f..f1748b97e 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -259,7 +259,11 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, opj_stream_set_skip_function(stream, j2k_skip); opj_stream_set_seek_function(stream, j2k_seek); +#if OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 0 opj_stream_set_user_data(stream, encoder); +#else + opj_stream_set_user_data(stream, encoder, NULL); +#endif /* Setup an opj_image */ if (strcmp (im->mode, "L") == 0) { diff --git a/setup.py b/setup.py index 50ca985e3..a7dd94190 100644 --- a/setup.py +++ b/setup.py @@ -337,14 +337,23 @@ class pil_build_ext(build_ext): _add_directory(include_dirs, "/usr/include") # on Windows, look for the OpenJPEG libraries in the location that - # the official installed puts them + # the official installer puts them if sys.platform == "win32": - _add_directory(library_dirs, - os.path.join(os.environ.get("ProgramFiles", ""), - "OpenJPEG 2.0", "lib")) - _add_directory(include_dirs, - os.path.join(os.environ.get("ProgramFiles", ""), - "OpenJPEG 2.0", "include")) + program_files = os.environ.get('ProgramFiles', '') + best_version = (0, 0) + best_path = None + for name in os.listdir(program_files): + if name.startswith('OpenJPEG '): + version = tuple([int(x) for x in name[9:].strip().split('.')]) + if version > best_version: + best_version = version + best_path = os.path.join(program_files, name) + + if best_path: + _add_directory(library_dirs, + os.path.join(best_path, 'lib')) + _add_directory(include_dirs, + os.path.join(best_path, 'include')) # # insert new dirs *before* default libs, to avoid conflicts @@ -375,10 +384,28 @@ class pil_build_ext(build_ext): feature.jpeg = "libjpeg" # alternative name if feature.want('jpeg2000'): - if _find_include_file(self, "openjpeg-2.0/openjpeg.h"): - if _find_library_file(self, "openjp2"): - feature.jpeg2000 = "openjp2" - + best_version = None + best_path = None + + # Find the best version + for directory in self.compiler.include_dirs: + for name in os.listdir(directory): + if name.startswith('openjpeg-') and \ + os.path.isfile(os.path.join(directory, name, + 'openjpeg.h')): + version = tuple([int(x) for x in name[9:].split('.')]) + if best_version is None or version > best_version: + best_version = version + best_path = os.path.join(directory, name) + + if best_version and _find_library_file(self, 'openjp2'): + # Add the directory to the include path so we can include + # rather than having to cope with the versioned + # include path + _add_directory(self.compiler.include_dirs, best_path, 0) + feature.jpeg2000 = 'openjp2' + feature.openjpeg_version = '.'.join([str(x) for x in best_version]) + if feature.want('tiff'): if _find_library_file(self, "tiff"): feature.tiff = "tiff" @@ -572,7 +599,7 @@ class pil_build_ext(build_ext): options = [ (feature.tcl and feature.tk, "TKINTER"), (feature.jpeg, "JPEG"), - (feature.jpeg2000, "OPENJPEG (JPEG2000)"), + (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), (feature.zlib, "ZLIB (PNG/ZIP)"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), @@ -583,7 +610,10 @@ class pil_build_ext(build_ext): all = 1 for option in options: if option[0]: - print("--- %s support available" % option[1]) + version = '' + if len(option) >= 3: + version = ' (%s)' % option[2] + print("--- %s support available%s" % (option[1], version)) else: print("*** %s support not available" % option[1]) if option[1] == "TKINTER" and _tkinter: From 72ecb8752e283c199f14ed29f15336d0a42cdd41 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 27 May 2014 12:52:50 +0100 Subject: [PATCH 042/488] Changed test for OpenJPEG 2.0 --- libImaging/Jpeg2KDecode.c | 3 ++- libImaging/Jpeg2KEncode.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 43a796c27..1b61b4f7d 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -517,7 +517,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state, opj_stream_set_read_function(stream, j2k_read); opj_stream_set_skip_function(stream, j2k_skip); -#if OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 0 + /* OpenJPEG 2.0 doesn't have OPJ_VERSION_MAJOR */ +#ifndef OPJ_VERSION_MAJOR opj_stream_set_user_data(stream, decoder); #else opj_stream_set_user_data(stream, decoder, NULL); diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index f1748b97e..8e7d0d1f2 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -259,7 +259,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, opj_stream_set_skip_function(stream, j2k_skip); opj_stream_set_seek_function(stream, j2k_seek); -#if OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 0 + /* OpenJPEG 2.0 doesn't have OPJ_VERSION_MAJOR */ +#ifndef OPJ_VERSION_MAJOR opj_stream_set_user_data(stream, encoder); #else opj_stream_set_user_data(stream, encoder, NULL); From 87d10dcaebec1ce9b3c2217c592c07da76e54252 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 27 May 2014 15:05:25 +0100 Subject: [PATCH 043/488] Oops. Fixed a silly mistake. --- PIL/Jpeg2KImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 31e6f0a13..6b5fbdedc 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -166,9 +166,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile): elif hasattr(self.fp, "seek"): try: pos = f.tell() - seek(0, 2) + f.seek(0, 2) length = f.tell() - seek(pos, 0) + f.seek(pos, 0) except: length = -1 From ae37743ac743bc4f23cc7caa787a541fdb3e9651 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 27 May 2014 16:58:26 +0300 Subject: [PATCH 044/488] Both kinds of bounding box for arc, chord and pieslice. --- Tests/test_imagedraw.py | 6 +-- _imaging.c | 109 ++++++++++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 29 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 2e0cc07c0..c47638a05 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -62,7 +62,7 @@ def helper_arc(bbox): def test_arc1(): - assert_exception(TypeError, lambda: helper_arc(bbox1)) + helper_arc(bbox1) def test_arc2(): @@ -97,7 +97,7 @@ def helper_chord(bbox): def test_chord1(): - assert_exception(TypeError, lambda: helper_chord(bbox1)) + helper_chord(bbox1) def test_chord2(): @@ -160,7 +160,7 @@ def helper_pieslice(bbox): def test_pieslice1(): - assert_exception(TypeError, lambda: helper_pieslice(bbox1)) + helper_pieslice(bbox1) def test_pieslice2(): diff --git a/_imaging.c b/_imaging.c index c47868b81..f8fd96f8a 100644 --- a/_imaging.c +++ b/_imaging.c @@ -2252,17 +2252,17 @@ void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){ if (bytes) { *text = (unsigned char*)PyBytes_AsString(bytes); return; - } + } #if PY_VERSION_HEX < 0x03000000 /* likely case here is py2.x with an ordinary string. but this isn't defined in Py3.x */ if (PyString_Check(encoded_string)) { *text = (unsigned char *)PyString_AsString(encoded_string); - } + } #endif } - + static PyObject* _font_getmask(ImagingFontObject* self, PyObject* args) @@ -2336,7 +2336,7 @@ _font_getsize(ImagingFontObject* self, PyObject* args) return NULL; } - return Py_BuildValue("ii", textwidth(self, text), self->ysize); + return Py_BuildValue("ii", textwidth(self, text), self->ysize); } static struct PyMethodDef _font_methods[] = { @@ -2399,17 +2399,35 @@ _draw_ink(ImagingDrawObject* self, PyObject* args) static PyObject* _draw_arc(ImagingDrawObject* self, PyObject* args) { - int x0, y0, x1, y1; + double* xy; + int n; + + PyObject* data; int ink; int start, end; int op = 0; - if (!PyArg_ParseTuple(args, "(iiii)iii|i", - &x0, &y0, &x1, &y1, - &start, &end, &ink)) + if (!PyArg_ParseTuple(args, "Oiii|i", &data, &start, &end, &ink)) return NULL; - if (ImagingDrawArc(self->image->image, x0, y0, x1, y1, start, end, - &ink, op) < 0) + n = PyPath_Flatten(data, &xy); + if (n < 0) + return NULL; + if (n != 2) { + PyErr_SetString(PyExc_TypeError, + "coordinate list must contain exactly 2 coordinates" + ); + return NULL; + } + + n = ImagingDrawArc(self->image->image, + (int) xy[0], (int) xy[1], + (int) xy[2], (int) xy[3], + start, end, &ink, op + ); + + free(xy); + + if (n < 0) return NULL; Py_INCREF(Py_None); @@ -2455,15 +2473,35 @@ _draw_bitmap(ImagingDrawObject* self, PyObject* args) static PyObject* _draw_chord(ImagingDrawObject* self, PyObject* args) { - int x0, y0, x1, y1; + double* xy; + int n; + + PyObject* data; int ink, fill; int start, end; - if (!PyArg_ParseTuple(args, "(iiii)iiii", - &x0, &y0, &x1, &y1, &start, &end, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Oiiii", + &data, &start, &end, &ink, &fill)) return NULL; - if (ImagingDrawChord(self->image->image, x0, y0, x1, y1, - start, end, &ink, fill, self->blend) < 0) + n = PyPath_Flatten(data, &xy); + if (n < 0) + return NULL; + if (n != 2) { + PyErr_SetString(PyExc_TypeError, + "coordinate list must contain exactly 2 coordinates" + ); + return NULL; + } + + n = ImagingDrawChord(self->image->image, + (int) xy[0], (int) xy[1], + (int) xy[2], (int) xy[3], + start, end, &ink, fill, self->blend + ); + + free(xy); + + if (n < 0) return NULL; Py_INCREF(Py_None); @@ -2492,8 +2530,8 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) return NULL; } - n = ImagingDrawEllipse(self->image->image, - (int) xy[0], (int) xy[1], + n = ImagingDrawEllipse(self->image->image, + (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], &ink, fill, self->blend ); @@ -2656,15 +2694,34 @@ _draw_outline(ImagingDrawObject* self, PyObject* args) static PyObject* _draw_pieslice(ImagingDrawObject* self, PyObject* args) { - int x0, y0, x1, y1; + double* xy; + int n; + + PyObject* data; int ink, fill; int start, end; - if (!PyArg_ParseTuple(args, "(iiii)iiii", - &x0, &y0, &x1, &y1, &start, &end, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Oiiii", &data, &start, &end, &ink, &fill)) return NULL; - if (ImagingDrawPieslice(self->image->image, x0, y0, x1, y1, - start, end, &ink, fill, self->blend) < 0) + n = PyPath_Flatten(data, &xy); + if (n < 0) + return NULL; + if (n != 2) { + PyErr_SetString(PyExc_TypeError, + "coordinate list must contain exactly 2 coordinates" + ); + return NULL; + } + + n = ImagingDrawPieslice(self->image->image, + (int) xy[0], (int) xy[1], + (int) xy[2], (int) xy[3], + start, end, &ink, fill, self->blend + ); + + free(xy); + + if (n < 0) return NULL; Py_INCREF(Py_None); @@ -2738,9 +2795,9 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args) return NULL; } - n = ImagingDrawRectangle(self->image->image, + n = ImagingDrawRectangle(self->image->image, (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], + (int) xy[2], (int) xy[3], &ink, fill, self->blend ); @@ -3117,8 +3174,8 @@ _getattr_ptr(ImagingObject* self, void* closure) static PyObject* _getattr_unsafe_ptrs(ImagingObject* self, void* closure) { - return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", - "mode", self->image->mode, + return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", + "mode", self->image->mode, "type", self->image->type, "depth", self->image->depth, "bands", self->image->bands, From 4dcae4402cca5ed75e33c038778c291691095a7d Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 27 May 2014 23:55:43 +0300 Subject: [PATCH 045/488] Add back the other bounding box [CI skip] --- docs/reference/ImageDraw.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 45be43057..30aa15a9b 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -91,11 +91,11 @@ Methods Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. - :param xy: Four points to define the bounding box. Sequence of - ``[x0, y0, x1, y1]``. - :param start: Starting angle, in degrees. Angles are measured from + :param xy: Four points to define the bounding box. Sequence of + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + :param start: Starting angle, in degrees. Angles are measured from 3 o'clock, increasing clockwise. - :param end: Ending angle, in degrees. + :param end: Ending angle, in degrees. :param fill: Color to use for the arc. .. py:method:: PIL.ImageDraw.Draw.bitmap(xy, bitmap, fill=None) @@ -114,8 +114,8 @@ Methods Same as :py:meth:`~PIL.ImageDraw.Draw.arc`, but connects the end points with a straight line. - :param xy: Four points to define the bounding box. Sequence of - ``[x0, y0, x1, y1]``. + :param xy: Four points to define the bounding box. Sequence of + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. @@ -148,7 +148,7 @@ Methods center of the bounding box. :param xy: Four points to define the bounding box. Sequence of - ``[x0, y0, x1, y1]``. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. From 70a50907c293f68ee9787b7663d78cb92291aa1c Mon Sep 17 00:00:00 2001 From: Chris Sinchok Date: Wed, 28 May 2014 17:21:58 -0500 Subject: [PATCH 046/488] This patch allows a JPEG image to be saved with a specific qtables value (in dictionary format). Previously, this would throw a TypeError when checking if the qtables value was actually a preset. By adding an isStringType check, we can avoid this error. --- PIL/JpegImagePlugin.py | 2 +- Tests/test_file_jpeg.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 7b40d5d4f..fbf4248d9 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -498,7 +498,7 @@ def _save(im, fp, filename): else: if subsampling in presets: subsampling = presets[subsampling].get('subsampling', -1) - if qtables in presets: + if isStringType(qtables) and qtables in presets: qtables = presets[qtables].get('quantization') if subsampling == "4:4:4": diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 4871c3fbf..c9ee4eec2 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -230,6 +230,13 @@ def test_quality_keep(): assert_no_exception(lambda: im.save(f, quality='keep')) +def test_qtables(): + im = Image.open("Images/lena.jpg") + qtables = im.quantization + f = tempfile('temp.jpg') + assert_no_exception(lambda: im.save(f, qtables=qtables, subsampling=0)) + + def test_junk_jpeg_header(): # https://github.com/python-imaging/Pillow/issues/630 filename = "Tests/images/junk_jpeg_header.jpg" From f4ddf1be9775c4a034704ba19c53e7e9ea4703ab Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 30 May 2014 15:08:21 -0700 Subject: [PATCH 047/488] Initialize openjpeg_version --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a7dd94190..d385ba8a2 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,7 @@ class pil_build_ext(build_ext): class feature: zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None - jpeg2000 = None + jpeg2000 = openjpeg_version = None required = [] def require(self, feat): @@ -611,7 +611,7 @@ class pil_build_ext(build_ext): for option in options: if option[0]: version = '' - if len(option) >= 3: + if len(option) >= 3 and option[2]: version = ' (%s)' % option[2] print("--- %s support available%s" % (option[1], version)) else: From 6dda7601afed8d0f26c8535704001b358c1b24ce Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 30 May 2014 15:37:35 -0700 Subject: [PATCH 048/488] Testing against newer version of OpenJPEG --- depends/install_openjpeg.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index bd6b83e3b..b3a6ccc4d 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -2,15 +2,16 @@ # install openjpeg -if [ ! -f openjpeg-2.0.0.tar.gz ]; then - wget 'https://openjpeg.googlecode.com/files/openjpeg-2.0.0.tar.gz' +if [ ! -f openjpeg-2.1.0.tar.gz ]; then + wget 'http://iweb.dl.sourceforge.net/project/openjpeg.mirror/2.1.0/openjpeg-2.1.0.tar.gz' + fi -rm -r openjpeg-2.0.0 -tar -xvzf openjpeg-2.0.0.tar.gz +rm -r openjpeg-2.1.0 +tar -xvzf openjpeg-2.1.0.tar.gz -pushd openjpeg-2.0.0 +pushd openjpeg-2.1.0 cmake -DCMAKE_INSTALL_PREFIX=/usr . && make && sudo make install From bc2c7bee7018aab85cc5f7f29eaea267c863790c Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Fri, 30 May 2014 15:47:27 -0700 Subject: [PATCH 049/488] Defer initialization of openjpeg_version --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d385ba8a2..30d6e17d5 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,7 @@ class pil_build_ext(build_ext): class feature: zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None - jpeg2000 = openjpeg_version = None + jpeg2000 = None required = [] def require(self, feat): @@ -383,6 +383,7 @@ class pil_build_ext(build_ext): _find_library_file(self, "libjpeg")): feature.jpeg = "libjpeg" # alternative name + feature.openjpeg_version = None if feature.want('jpeg2000'): best_version = None best_path = None From 89130203b1f583fecf398fb65ef42e3f8e1533db Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jun 2014 09:10:52 -0700 Subject: [PATCH 050/488] Update CHANGES.rst [ci-skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a8dd7d137..6b7d0162f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Added more ImageDraw tests + [hugovk] + - Added tests for Spider files [hugovk] From 27d49b6f27046221da93236963950c3bb2b07d77 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 09:57:49 +0300 Subject: [PATCH 051/488] pep8 --- PIL/ImageCms.py | 225 ++++++++++++++++++++++++++++-------------------- 1 file changed, 132 insertions(+), 93 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 5decaf704..fc176952e 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -66,7 +66,8 @@ pyCMS Added try/except statements arount type() checks of potential CObjects... Python won't let you use type() - on them, and raises a TypeError (stupid, if you ask me!) + on them, and raises a TypeError (stupid, if you ask + me!) Added buildProofTransformFromOpenProfiles() function. Additional fixes in DLL, see DLL code for details. @@ -115,7 +116,9 @@ FLAGS = { "MATRIXOUTPUT": 2, "MATRIXONLY": (1 | 2), "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot - "NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) + # Don't create prelinearization tables on precalculated transforms + # (internal use): + "NOPRELINEARIZATION": 16, "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) "NOTCACHE": 64, # Inhibit 1-pixel cache "NOTPRECALC": 256, @@ -168,13 +171,14 @@ class ImageCmsProfile: class ImageCmsTransform(Image.ImagePointHandler): + """Transform. This can be used with the procedural API, or with the standard Image.point() method. """ def __init__(self, input, output, input_mode, output_mode, - intent=INTENT_PERCEPTUAL, - proof=None, proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): + intent=INTENT_PERCEPTUAL, proof=None, + proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): if proof is None: self.transform = core.buildTransform( input.profile, output.profile, @@ -238,11 +242,15 @@ def get_display_profile(handle=None): # --------------------------------------------------------------------. class PyCMSError(Exception): - """ (pyCMS) Exception class. This is used for all errors in the pyCMS API. """ + + """ (pyCMS) Exception class. + This is used for all errors in the pyCMS API. """ pass -def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0): +def profileToProfile( + im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, + outputMode=None, inPlace=0, flags=0): """ (pyCMS) Applies an ICC transformation to a given image, mapping from inputProfile to outputProfile. @@ -264,29 +272,33 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER profiles, the input profile must handle RGB data, and the output profile must handle CMYK data. - :param im: An open PIL image object (i.e. Image.new(...) or Image.open(...), etc.) - :param inputProfile: String, as a valid filename path to the ICC input profile - you wish to use for this image, or a profile object + :param im: An open PIL image object (i.e. Image.new(...) or + Image.open(...), etc.) + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this image, or a profile object :param outputProfile: String, as a valid filename path to the ICC output profile you wish to use for this image, or a profile object - :param renderingIntent: Integer (0-3) specifying the rendering intent you wish - to use for the transform + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the transform INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. - :param outputMode: A valid PIL mode for the output image (i.e. "RGB", "CMYK", - etc.). Note: if rendering the image "inPlace", outputMode MUST be the - same mode as the input, or omitted completely. If omitted, the outputMode - will be the same as the mode of the input image (im.mode) - :param inPlace: Boolean (1 = True, None or 0 = False). If True, the original - image is modified in-place, and None is returned. If False (default), a - new Image object is returned with the transform applied. + see the pyCMS documentation for details on rendering intents and what + they do. + :param outputMode: A valid PIL mode for the output image (i.e. "RGB", + "CMYK", etc.). Note: if rendering the image "inPlace", outputMode + MUST be the same mode as the input, or omitted completely. If + omitted, the outputMode will be the same as the mode of the input + image (im.mode) + :param inPlace: Boolean (1 = True, None or 0 = False). If True, the + original image is modified in-place, and None is returned. If False + (default), a new Image object is returned with the transform applied. :param flags: Integer (0-...) specifying additional flags - :returns: Either None or a new PIL image object, depending on value of inPlace + :returns: Either None or a new PIL image object, depending on value of + inPlace :exception PyCMSError: """ @@ -297,7 +309,8 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError( + "flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -329,8 +342,8 @@ def getOpenProfile(profileFilename): If profileFilename is not a vaild filename for an ICC profile, a PyCMSError will be raised. - :param profileFilename: String, as a valid filename path to the ICC profile you - wish to open, or a file-like object. + :param profileFilename: String, as a valid filename path to the ICC profile + you wish to open, or a file-like object. :returns: A CmsProfile class object. :exception PyCMSError: """ @@ -341,7 +354,9 @@ def getOpenProfile(profileFilename): raise PyCMSError(v) -def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0): +def buildTransform( + inputProfile, outputProfile, inMode, outMode, + renderingIntent=INTENT_PERCEPTUAL, flags=0): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the outputProfile. Use applyTransform to apply the transform to a given @@ -374,14 +389,14 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent manually overridden if you really want to, but I don't know of any time that would be of use, or would even work). - :param inputProfile: String, as a valid filename path to the ICC input profile - you wish to use for this transform, or a profile object + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object :param outputProfile: String, as a valid filename path to the ICC output profile you wish to use for this transform, or a profile object - :param inMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) - :param outMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the transform @@ -390,7 +405,8 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param flags: Integer (0-...) specifying additional flags :returns: A CmsTransform class object. :exception PyCMSError: @@ -400,19 +416,26 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError( + "flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): inputProfile = ImageCmsProfile(inputProfile) if not isinstance(outputProfile, ImageCmsProfile): outputProfile = ImageCmsProfile(outputProfile) - return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags) + return ImageCmsTransform( + inputProfile, outputProfile, inMode, outMode, + renderingIntent, flags=flags) except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) -def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]): +def buildProofTransform( + inputProfile, outputProfile, proofProfile, inMode, outMode, + renderingIntent=INTENT_PERCEPTUAL, + proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, + flags=FLAGS["SOFTPROOFING"]): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the outputProfile, but tries to simulate the result that would be @@ -451,17 +474,17 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo when the simulated device has a much wider gamut than the output device, you may obtain marginal results. - :param inputProfile: String, as a valid filename path to the ICC input profile - you wish to use for this transform, or a profile object + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object :param outputProfile: String, as a valid filename path to the ICC output (monitor, usually) profile you wish to use for this transform, or a profile object - :param proofProfile: String, as a valid filename path to the ICC proof profile - you wish to use for this transform, or a profile object - :param inMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) - :param outMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param proofProfile: String, as a valid filename path to the ICC proof + profile you wish to use for this transform, or a profile object + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the input->proof (simulated) transform @@ -470,7 +493,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param proofRenderingIntent: Integer (0-3) specifying the rendering intent you wish to use for proof->output transform @@ -479,7 +503,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param flags: Integer (0-...) specifying additional flags :returns: A CmsTransform class object. :exception PyCMSError: @@ -489,7 +514,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError( + "flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -498,7 +524,9 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo outputProfile = ImageCmsProfile(outputProfile) if not isinstance(proofProfile, ImageCmsProfile): proofProfile = ImageCmsProfile(proofProfile) - return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, proofProfile, proofRenderingIntent, flags) + return ImageCmsTransform( + inputProfile, outputProfile, inMode, outMode, renderingIntent, + proofProfile, proofRenderingIntent, flags) except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -523,8 +551,8 @@ def applyTransform(im, transform, inPlace=0): is raised. This function applies a pre-calculated transform (from - ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) to an - image. The transform can be used for multiple images, saving + ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) + to an image. The transform can be used for multiple images, saving considerable calcuation time if doing the same conversion multiple times. If you want to modify im in-place instead of receiving a new image as @@ -537,10 +565,12 @@ def applyTransform(im, transform, inPlace=0): :param im: A PIL Image object, and im.mode must be the same as the inMode supported by the transform. :param transform: A valid CmsTransform class object - :param inPlace: Bool (1 == True, 0 or None == False). If True, im is modified - in place and None is returned, if False, a new Image object with the - transform applied is returned (and im is not changed). The default is False. - :returns: Either None, or a new PIL Image object, depending on the value of inPlace + :param inPlace: Bool (1 == True, 0 or None == False). If True, im is + modified in place and None is returned, if False, a new Image object + with the transform applied is returned (and im is not changed). The + default is False. + :returns: Either None, or a new PIL Image object, depending on the value of + inPlace :exception PyCMSError: """ @@ -572,24 +602,29 @@ def createProfile(colorSpace, colorTemp=-1): ImageCms.buildTransformFromOpenProfiles() to create a transform to apply to images. - :param colorSpace: String, the color space of the profile you wish to create. + :param colorSpace: String, the color space of the profile you wish to + create. Currently only "LAB", "XYZ", and "sRGB" are supported. :param colorTemp: Positive integer for the white point for the profile, in degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 - illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles, - and is ignored for XYZ and sRGB. + illuminant if omitted (5000k). colorTemp is ONLY applied to LAB + profiles, and is ignored for XYZ and sRGB. :returns: A CmsProfile class object :exception PyCMSError: """ if colorSpace not in ["LAB", "XYZ", "sRGB"]: - raise PyCMSError("Color space not supported for on-the-fly profile creation (%s)" % colorSpace) + raise PyCMSError( + "Color space not supported for on-the-fly profile creation (%s)" + % colorSpace) if colorSpace == "LAB": try: colorTemp = float(colorTemp) except: - raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp) + raise PyCMSError( + "Color temperature must be numeric, \"%s\" not valid" + % colorTemp) try: return core.createProfile(colorSpace, colorTemp) @@ -611,10 +646,10 @@ def getProfileName(profile): profile was originally created. Sometimes this tag also contains additional information supplied by the creator. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal name of the profile as stored in an - ICC tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal name of the profile as stored + in an ICC tag. :exception PyCMSError: """ @@ -653,10 +688,10 @@ def getProfileInfo(profile): info tag. This often contains details about the profile, and how it was created, as supplied by the creator. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ @@ -664,7 +699,8 @@ def getProfileInfo(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) # add an extra newline to preserve pyCMS compatibility - # Python, not C. the white point bits weren't working well, so skipping. + # Python, not C. the white point bits weren't working well, + # so skipping. # // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint description = profile.profile.product_description cpright = profile.profile.product_copyright @@ -691,10 +727,10 @@ def getProfileCopyright(profile): Use this function to obtain the information stored in the profile's copyright tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ try: @@ -713,16 +749,16 @@ def getProfileManufacturer(profile): If profile isn't a valid CmsProfile object or filename to a profile, a PyCMSError is raised. - If an error occurs while trying to obtain the manufacturer tag, a PyCMSError - is raised + If an error occurs while trying to obtain the manufacturer tag, a + PyCMSError is raised Use this function to obtain the information stored in the profile's manufacturer tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ try: @@ -747,10 +783,10 @@ def getProfileModel(profile): Use this function to obtain the information stored in the profile's model tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ @@ -776,10 +812,10 @@ def getProfileDescription(profile): Use this function to obtain the information stored in the profile's description tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in an + ICC tag. :exception PyCMSError: """ @@ -808,16 +844,18 @@ def getDefaultIntent(profile): If you wish to use a different intent than returned, use ImageCms.isIntentSupported() to verify it will work first. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: Integer 0-3 specifying the default rendering intent for this profile. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: Integer 0-3 specifying the default rendering intent for this + profile. INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :exception PyCMSError: """ @@ -844,17 +882,18 @@ def isIntentSupported(profile, intent, direction): potential PyCMSError that will occur if they don't support the modes you select. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :param intent: Integer (0-3) specifying the rendering intent you wish to use - with this profile + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :param intent: Integer (0-3) specifying the rendering intent you wish to + use with this profile INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param direction: Integer specifing if the profile is to be used for input, output, or proof From e9830572947243d7d10d786b304804fee43b059d Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 12:41:48 +0300 Subject: [PATCH 052/488] pep8/pyflakes --- Tests/test_imagecms.py | 84 +++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index dcb445c9f..8a7c172b2 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -9,12 +9,13 @@ except ImportError: SRGB = "Tests/icc/sRGB.icm" + def test_sanity(): # basic smoke test. # this mostly follows the cms_test outline. - v = ImageCms.versions() # should return four strings + v = ImageCms.versions() # should return four strings assert_equal(v[0], '1.0.0 pil') assert_equal(list(map(type, v)), [str, str, str, str]) @@ -41,12 +42,15 @@ def test_sanity(): assert_image(i, "RGB", (128, 128)) # test PointTransform convenience API - im = lena().point(t) + lena().point(t) + def test_name(): # get profile information for file assert_equal(ImageCms.getProfileName(SRGB).strip(), 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def x_test_info(): assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), ['sRGB IEC61966-2.1', '', @@ -54,11 +58,13 @@ def x_test_info(): 'WhitePoint : D65 (daylight)', '', 'Tests/icc/sRGB.icm']) + def test_intent(): assert_equal(ImageCms.getDefaultIntent(SRGB), 0) assert_equal(ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + def test_profile_object(): # same, using profile object @@ -69,8 +75,9 @@ def test_profile_object(): # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) assert_equal(ImageCms.getDefaultIntent(p), 0) assert_equal(ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + def test_extensions(): # extensions @@ -79,12 +86,21 @@ def test_extensions(): assert_equal(ImageCms.getProfileName(p).strip(), 'IEC 61966-2.1 Default RGB colour space - sRGB') + def test_exceptions(): # the procedural pyCMS API uses PyCMSError for all sorts of errors - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.getProfileName(None)) - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.isIntentSupported(SRGB, None, None)) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.getProfileName(None)) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.isIntentSupported(SRGB, None, None)) def test_display_profile(): @@ -93,61 +109,63 @@ def test_display_profile(): def test_lab_color_profile(): - pLab = ImageCms.createProfile("LAB", 5000) - pLab = ImageCms.createProfile("LAB", 6500) + ImageCms.createProfile("LAB", 5000) + ImageCms.createProfile("LAB", 6500) + def test_simple_lab(): - i = Image.new('RGB', (10,10), (128,128,128)) + i = Image.new('RGB', (10, 10), (128, 128, 128)) - pLab = ImageCms.createProfile("LAB") + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") i_lab = ImageCms.applyTransform(i, t) - assert_equal(i_lab.mode, 'LAB') - k = i_lab.getpixel((0,0)) - assert_equal(k, (137,128,128)) # not a linear luminance map. so L != 128 + k = i_lab.getpixel((0, 0)) + assert_equal(k, (137, 128, 128)) # not a linear luminance map. so L != 128 - L = i_lab.getdata(0) + L = i_lab.getdata(0) a = i_lab.getdata(1) b = i_lab.getdata(2) - assert_equal(list(L), [137]*100) - assert_equal(list(a), [128]*100) - assert_equal(list(b), [128]*100) + assert_equal(list(L), [137] * 100) + assert_equal(list(a), [128] * 100) + assert_equal(list(b), [128] * 100) + - def test_lab_color(): - pLab = ImageCms.createProfile("LAB") + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - # need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType, - # and have that mapping work back to a PIL mode. (likely RGB) + # Need to add a type mapping for some PIL type to TYPE_Lab_8 in + # findLCMSType, and have that mapping work back to a PIL mode (likely RGB). i = ImageCms.applyTransform(lena(), t) assert_image(i, "LAB", (128, 128)) - - # i.save('temp.lab.tif') # visually verified vs PS. + + # i.save('temp.lab.tif') # visually verified vs PS. target = Image.open('Tests/images/lena.Lab.tif') assert_image_similar(i, target, 30) + def test_lab_srgb(): - pLab = ImageCms.createProfile("LAB") + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") img = Image.open('Tests/images/lena.Lab.tif') img_srgb = ImageCms.applyTransform(img, t) - # img_srgb.save('temp.srgb.tif') # visually verified vs ps. - + # img_srgb.save('temp.srgb.tif') # visually verified vs ps. + assert_image_similar(lena(), img_srgb, 30) + def test_lab_roundtrip(): - # check to see if we're at least internally consistent. - pLab = ImageCms.createProfile("LAB") + # check to see if we're at least internally consistent. + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") @@ -156,5 +174,3 @@ def test_lab_roundtrip(): out = ImageCms.applyTransform(i, t2) assert_image_similar(lena(), out, 2) - - From 9e984a19b11e7e7ba7826550eccff4c5d97be576 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 13:00:21 +0300 Subject: [PATCH 053/488] Add more tests for ImageCms --- Tests/test_imagecms.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 8a7c172b2..5b3859c60 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -25,6 +25,10 @@ def test_sanity(): i = ImageCms.profileToProfile(lena(), SRGB, SRGB) assert_image(i, "RGB", (128, 128)) + i = lena() + ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) + assert_image(i, "RGB", (128, 128)) + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") i = ImageCms.applyTransform(lena(), t) assert_image(i, "RGB", (128, 128)) @@ -51,12 +55,25 @@ def test_name(): 'IEC 61966-2.1 Default RGB colour space - sRGB') -def x_test_info(): +def test_info(): assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), ['sRGB IEC61966-2.1', '', - 'Copyright (c) 1998 Hewlett-Packard Company', '', - 'WhitePoint : D65 (daylight)', '', - 'Tests/icc/sRGB.icm']) + 'Copyright (c) 1998 Hewlett-Packard Company', '']) + + +def test_copyright(): + assert_equal(ImageCms.getProfileCopyright(SRGB).strip(), + 'Copyright (c) 1998 Hewlett-Packard Company') + + +def test_manufacturer(): + assert_equal(ImageCms.getProfileManufacturer(SRGB).strip(), + 'IEC http://www.iec.ch') + + +def test_description(): + assert_equal(ImageCms.getProfileDescription(SRGB).strip(), + 'sRGB IEC61966-2.1') def test_intent(): From 6115d348b983118bd43fb7c54e8d0a25d2c6fca2 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 13:19:01 +0300 Subject: [PATCH 054/488] Add more tests for ImageCms --- Tests/test_imagecms.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 5b3859c60..f52101eb1 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -33,6 +33,11 @@ def test_sanity(): i = ImageCms.applyTransform(lena(), t) assert_image(i, "RGB", (128, 128)) + i = lena() + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + ImageCms.applyTransform(lena(), t, inPlace=True) + assert_image(i, "RGB", (128, 128)) + p = ImageCms.createProfile("sRGB") o = ImageCms.getOpenProfile(SRGB) t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") @@ -71,6 +76,11 @@ def test_manufacturer(): 'IEC http://www.iec.ch') +def test_model(): + assert_equal(ImageCms.getProfileModel(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_description(): assert_equal(ImageCms.getProfileDescription(SRGB).strip(), 'sRGB IEC61966-2.1') From beedca78e3c33e5e4c50d2a7389e6ea6762a0dea Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 15:57:50 +0300 Subject: [PATCH 055/488] Show counts of pep8/pyflakes after build --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36dae5b7a..1d313a088 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ after_success: - coverage report - coveralls - pip install pep8 pyflakes - - pep8 PIL/*.py - - pyflakes PIL/*.py - - pep8 Tests/*.py - - pyflakes Tests/*.py + - pep8 --statistics --count PIL/*.py + - pep8 --statistics --count Tests/*.py + - pyflakes PIL/*.py | tee >(wc -l) + - pyflakes Tests/*.py | tee >(wc -l) From e944297e80b93aae86769830e4dc9f80a6bde66a Mon Sep 17 00:00:00 2001 From: joaoxsouls Date: Sun, 1 Jun 2014 23:57:25 +0100 Subject: [PATCH 056/488] add FreeBSD10 support to doc --- docs/installation.rst | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 94054df82..76c7d4ef5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -67,11 +67,11 @@ Many of Pillow's features require external libraries: * Pillow has been tested with version **0.1.3**, which does not read transparent webp files. Versions **0.3.0** and **0.4.0** support - transparency. + transparency. -* **tcl/tk** provides support for tkinter bitmap and photo images. +* **tcl/tk** provides support for tkinter bitmap and photo images. -* **openjpeg** provides JPEG 2000 functionality. +* **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**. @@ -108,7 +108,7 @@ Or for Python 3:: $ sudo apt-get install python3-dev python3-setuptools In Fedora, the command is:: - + $ sudo yum install python-devel Prerequisites are installed on **Ubuntu 10.04 LTS** with:: @@ -185,6 +185,25 @@ to a specific version: $ pip install --use-wheel Pillow==2.3.0 +FreeBSD installation +--------------------- + +.. Note:: Only FreeBSD 10 tested + + +Make sure you have Python's development libraries installed.:: + + $ sudo pkg install python2 + +Or for Python 3:: + + $ sudo pkg install python3 + +Prerequisites are installed on **FreeBSD 10** with:: + + $ sudo pkg install jpeg tiff webp lcms2 freetype2 + + Platform support ---------------- @@ -199,7 +218,7 @@ current versions of Linux, OS X, and Windows. Contributors please test on your platform, edit this document, and send a pull request. -+----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ ++----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ |**Operating system** |**Supported**|**Tested Python versions** |**Tested Pillow versions** |**Tested processors** | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Mac OS X 10.8 Mountain Lion |Yes | 2.6,2.7,3.2,3.3 | |x86-64 | @@ -224,6 +243,8 @@ current versions of Linux, OS X, and Windows. +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Gentoo Linux |Yes | 2.7,3.2 | 2.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ +| FreeBSD 10 |Yes | 2.7,3.4 | 2.4,2.3.1 |x86-64 | ++----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows 7 Pro |Yes | 2.7,3.2,3.3 | 2.2.1 |x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows Server 2008 R2 Enterprise|Yes | 3.3 | |x86-64 | @@ -232,4 +253,3 @@ current versions of Linux, OS X, and Windows. +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows 8.1 Pro |Yes | 2.6,2.7,3.2,3.3,3.4 | 2.3.0, 2.4.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ - From c05dc710757a09a1a7228c92e25659d6f70b1947 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 2 Jun 2014 12:22:45 -0700 Subject: [PATCH 057/488] Update docs for OpenJPEG version --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 94054df82..0a6fcc6fd 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -73,7 +73,7 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. - * Pillow has been tested with openjpeg **2.0.0**. + * Pillow has been tested with openjpeg **2.0.0** and **2.1.0**. If the prerequisites are installed in the standard library locations for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no additional configuration From 9dc2346dead9853edec57514cdd99ccf4898d0e8 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 3 Jun 2014 11:06:27 +0300 Subject: [PATCH 058/488] Fix Travis CI badge for new python-pillow name --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 277c5d01f..33b9813d0 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ Pillow Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. -.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master - :target: https://travis-ci.org/python-imaging/Pillow +.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master + :target: https://travis-ci.org/python-pillow/Pillow :alt: Travis CI build status .. image:: https://pypip.in/v/Pillow/badge.png From c9a4272af62f96974a1651c0fe877337aec79bc9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 3 Jun 2014 13:02:44 +0300 Subject: [PATCH 059/488] Replace python-imaging with python-pillow (but yet not Coveralls) --- CHANGES.rst | 38 ++++++++++++++++---------------- PIL/TiffImagePlugin.py | 20 ++++++++--------- Tests/test_file_gif.py | 16 +++++++------- Tests/test_file_jpeg.py | 6 ++--- Tests/test_file_libtiff.py | 38 ++++++++++++++++---------------- Tests/test_file_png.py | 10 ++++----- Tests/test_file_tiff_metadata.py | 16 +++++++------- Tests/test_image_convert.py | 4 ++-- Tests/test_image_getpixel.py | 10 ++++----- Tests/test_image_point.py | 4 ++-- Tests/test_image_transform.py | 38 ++++++++++++++++---------------- Tests/test_imagefile.py | 6 ++--- Tests/test_locale.py | 4 ++-- Tests/test_numpy.py | 14 ++++++------ docs/_templates/sidebarhelp.html | 2 +- docs/about.rst | 6 ++--- docs/index.rst | 8 +++---- setup.py | 8 +++---- 18 files changed, 124 insertions(+), 124 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6b7d0162f..fbe2d3b0c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,34 +8,34 @@ Changelog (Pillow) - Added tests for Spider files [hugovk] - + - Use libtiff to write any compressed tiff files [wiredfool] - + - Support for pickling Image objects [hugovk] - Fixed resolution handling for EPS thumbnails [eliempje] - + - Fixed rendering of some binary EPS files (Issue #302) - [eliempje] - + [eliempje] + - Rename variables not to use built-in function names [hugovk] - -- Ignore junk JPEG markers + +- Ignore junk JPEG markers [hugovk] - + - Change default interpolation for Image.thumbnail to Image.ANTIALIAS [hugovk] - + - Add tests and fixes for saving PDFs [hugovk] - + - Remove transparency resource after P->RGBA conversion [hugovk] - + - Clean up preprocessor cruft for Windows [CounterPillow] @@ -45,13 +45,13 @@ Changelog (Pillow) - Added Image.close, context manager support. [wiredfool] -- Added support for 16 bit PGM files. +- Added support for 16 bit PGM files. [wiredfool] - Updated OleFileIO to version 0.30 from upstream [hugovk] -- Added support for additional TIFF floating point format +- Added support for additional TIFF floating point format [Hijackal] - Have the tempfile use a suffix with a dot @@ -81,7 +81,7 @@ Changelog (Pillow) - Added support for JPEG 2000 [al45tair] -- Add more detailed error messages to Image.py +- Add more detailed error messages to Image.py [larsmans] - Avoid conflicting _expand functions in PIL & MINGW, fixes #538 @@ -109,7 +109,7 @@ Changelog (Pillow) [wiredfool] - Fixed palette handling when converting from mode P->RGB->P - [d_schmidt] + [d_schmidt] - Fixed saving mode P image as a PNG with transparency = palette color 0 [d-schmidt] @@ -119,7 +119,7 @@ Changelog (Pillow) - Fixed DOS with invalid palette size or invalid image size in BMP file [wiredfool] - + - Added support for BMP version 4 and 5 [eddwardo, wiredfool] @@ -152,7 +152,7 @@ Changelog (Pillow) - Prefer homebrew freetype over X11 freetype (but still allow both) [dmckeone] - + 2.3.1 (2014-03-14) ------------------ @@ -277,7 +277,7 @@ Changelog (Pillow) [nikmolnar] - Fix for encoding of b_whitespace, similar to closed issue #272 - [mhogg] + [mhogg] - Fix #273: Add numpy array interface support for 16 and 32 bit integer modes [cgohlke] @@ -437,7 +437,7 @@ Changelog (Pillow) - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) [fluggo] -- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67) +- Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67) - Add WebP support. [lqs] diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 11b92747c..34cb020aa 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -54,7 +54,7 @@ import collections import itertools import os -# Set these to true to force use of libtiff for reading or writing. +# Set these to true to force use of libtiff for reading or writing. READ_LIBTIFF = False WRITE_LIBTIFF= False @@ -238,7 +238,7 @@ class ImageFileDirectory(collections.MutableMapping): Value: integer corresponding to the data type from `TiffTags.TYPES` - 'internal' + 'internal' * self.tags = {} Key: numerical tiff tag number Value: Decoded data, Generally a tuple. * If set from __setval__ -- always a tuple @@ -489,10 +489,10 @@ class ImageFileDirectory(collections.MutableMapping): if tag in self.tagtype: typ = self.tagtype[tag] - + if Image.DEBUG: print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) - + if typ == 1: # byte data if isinstance(value, tuple): @@ -512,7 +512,7 @@ class ImageFileDirectory(collections.MutableMapping): # and doesn't match the tiff spec: 8-bit byte that # contains a 7-bit ASCII code; the last byte must be # NUL (binary zero). Also, I don't think this was well - # excersized before. + # excersized before. data = value = b"" + value.encode('ascii', 'replace') + b"\0" else: # integer data @@ -859,7 +859,7 @@ class TiffImageFile(ImageFile.ImageFile): # libtiff handles the fillmode for us, so 1;IR should # actually be 1;I. Including the R double reverses the # bits, so stripes of the image are reversed. See - # https://github.com/python-imaging/Pillow/issues/279 + # https://github.com/python-pillow/Pillow/issues/279 if fillorder == 2: key = ( self.tag.prefix, photo, format, 1, @@ -984,11 +984,11 @@ def _save(im, fp, filename): compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) - libtiff = WRITE_LIBTIFF or compression != 'raw' + libtiff = WRITE_LIBTIFF or compression != 'raw' # required for color libtiff images ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) - + # -- multi-page -- skip TIFF header on subsequent pages if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) @@ -1025,7 +1025,7 @@ def _save(im, fp, filename): # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] - + if "description" in im.encoderinfo: ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if "resolution" in im.encoderinfo: @@ -1091,7 +1091,7 @@ def _save(im, fp, filename): blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. atts={} - # bits per sample is a single short in the tiff directory, not a list. + # bits per sample is a single short in the tiff directory, not a list. atts[BITSPERSAMPLE] = bits[0] # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 4318e178e..566baa200 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -37,7 +37,7 @@ def test_roundtrip(): assert_image_similar(reread.convert('RGB'), im, 50) def test_roundtrip2(): - #see https://github.com/python-imaging/Pillow/issues/403 + #see https://github.com/python-pillow/Pillow/issues/403 out = tempfile('temp.gif') im = Image.open('Images/lena.gif') im2 = im.copy() @@ -48,11 +48,11 @@ def test_roundtrip2(): def test_palette_handling(): - # see https://github.com/python-imaging/Pillow/issues/513 + # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open('Images/lena.gif') im = im.convert('RGB') - + im = im.resize((100,100), Image.ANTIALIAS) im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) @@ -60,11 +60,11 @@ def test_palette_handling(): im2.save(f, optimize=True) reloaded = Image.open(f) - + assert_image_similar(im, reloaded.convert('RGB'), 10) - + def test_palette_434(): - # see https://github.com/python-imaging/Pillow/issues/434 + # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): out = tempfile('temp.gif') @@ -78,10 +78,10 @@ def test_palette_434(): assert_image_equal(*roundtrip(im)) assert_image_equal(*roundtrip(im, optimize=True)) - + im = im.convert("RGB") # check automatic P conversion reloaded = roundtrip(im)[1].convert('RGB') assert_image_equal(im, reloaded) - + diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 4871c3fbf..43ed55969 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -126,7 +126,7 @@ def test_optimize(): def test_optimize_large_buffer(): - # https://github.com/python-imaging/Pillow/issues/148 + # https://github.com/python-pillow/Pillow/issues/148 f = tempfile('temp.jpg') # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xff3333) @@ -159,7 +159,7 @@ def test_progressive_large_buffer_highest_quality(): def test_large_exif(): - # https://github.com/python-imaging/Pillow/issues/148 + # https://github.com/python-pillow/Pillow/issues/148 f = tempfile('temp.jpg') im = lena() im.save(f, 'JPEG', quality=90, exif=b"1"*65532) @@ -231,7 +231,7 @@ def test_quality_keep(): def test_junk_jpeg_header(): - # https://github.com/python-imaging/Pillow/issues/630 + # https://github.com/python-pillow/Pillow/issues/630 filename = "Tests/images/junk_jpeg_header.jpg" assert_no_exception(lambda: Image.open(filename)) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 58fa75239..fddff443a 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -71,7 +71,7 @@ def test_g4_eq_png(): assert_image_equal(g4, png) -# see https://github.com/python-imaging/Pillow/issues/279 +# see https://github.com/python-pillow/Pillow/issues/279 def test_g4_fillorder_eq_png(): """ Checking that we're actually getting the data that we expect""" png = Image.open('Tests/images/g4-fillorder-test.png') @@ -96,7 +96,7 @@ def test_g4_write(): assert_equal(reread.info['compression'], 'group4') assert_equal(reread.info['compression'], orig.info['compression']) - + assert_false(orig.tobytes() == reread.tobytes()) def test_adobe_deflate_tiff(): @@ -120,7 +120,7 @@ def test_write_metadata(): original = img.tag.named() reloaded = loaded.tag.named() - # PhotometricInterpretation is set from SAVE_INFO, not the original image. + # PhotometricInterpretation is set from SAVE_INFO, not the original image. ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'PhotometricInterpretation'] for tag, value in reloaded.items(): @@ -133,7 +133,7 @@ def test_write_metadata(): assert_equal(original[tag], value, "%s didn't roundtrip" % tag) for tag, value in original.items(): - if tag not in ignored: + if tag not in ignored: if tag.endswith('Resolution'): val = reloaded[tag] assert_almost_equal(val[0][0]/val[0][1], value[0][0]/value[0][1], @@ -164,7 +164,7 @@ def test_little_endian(): else: assert_equal(b[0], b'\xe0') assert_equal(b[1], b'\x01') - + out = tempfile("temp.tif") #out = "temp.le.tif" @@ -174,8 +174,8 @@ def test_little_endian(): assert_equal(reread.info['compression'], im.info['compression']) assert_equal(reread.getpixel((0,0)), 480) # UNDONE - libtiff defaults to writing in native endian, so - # on big endian, we'll get back mode = 'I;16B' here. - + # on big endian, we'll get back mode = 'I;16B' here. + def test_big_endian(): im = Image.open('Tests/images/16bit.MM.deflate.tif') @@ -191,7 +191,7 @@ def test_big_endian(): else: assert_equal(b[0], b'\x01') assert_equal(b[1], b'\xe0') - + out = tempfile("temp.tif") im.save(out) reread = Image.open(out) @@ -203,12 +203,12 @@ def test_g4_string_info(): """Tests String data in info directory""" file = "Tests/images/lena_g4_500.tif" orig = Image.open(file) - + out = tempfile("temp.tif") orig.tag[269] = 'temp.tif' orig.save(out) - + reread = Image.open(out) assert_equal('temp.tif', reread.tag[269]) @@ -223,8 +223,8 @@ def test_12bit_rawmode(): # convert 12bit.cropped.tif -depth 16 tmp.tif # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. - + # so we need to unshift so that the integer values are the same. + im2 = Image.open('Tests/images/12in16bit.tif') if Image.DEBUG: @@ -235,11 +235,11 @@ def test_12bit_rawmode(): print (im2.getpixel((0,0))) print (im2.getpixel((0,1))) print (im2.getpixel((0,2))) - + assert_image_equal(im, im2) def test_blur(): - # test case from irc, how to do blur on b/w image and save to compressed tif. + # test case from irc, how to do blur on b/w image and save to compressed tif. from PIL import ImageFilter out = tempfile('temp.tif') im = Image.open('Tests/images/pport_g4.tif') @@ -266,7 +266,7 @@ def test_compressions(): im.save(out, compression='jpeg') im2 = Image.open(out) assert_image_similar(im, im2, 30) - + def test_cmyk_save(): im = lena('CMYK') @@ -280,7 +280,7 @@ def xtest_bw_compression_wRGB(): """ This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to capture that but not now""" - + im = lena('RGB') out = tempfile('temp.tif') @@ -293,8 +293,8 @@ def test_fp_leak(): fn = im.fp.fileno() assert_no_exception(lambda: os.fstat(fn)) - im.load() # this should close it. - assert_exception(OSError, lambda: os.fstat(fn)) + im.load() # this should close it. + assert_exception(OSError, lambda: os.fstat(fn)) im = None # this should force even more closed. - assert_exception(OSError, lambda: os.fstat(fn)) + assert_exception(OSError, lambda: os.fstat(fn)) assert_exception(OSError, lambda: os.close(fn)) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c17829194..0b0ad3ca7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -101,7 +101,7 @@ def test_bad_text(): assert_equal(im.info, {'spam': 'egg\x00'}) def test_bad_ztxt(): - # Test reading malformed zTXt chunks (python-imaging/Pillow#318) + # Test reading malformed zTXt chunks (python-pillow/Pillow#318) im = load(HEAD + chunk(b'zTXt') + TAIL) assert_equal(im.info, {}) @@ -182,7 +182,7 @@ def test_save_l_transparency(): file = tempfile("temp.png") assert_no_exception(lambda: im.save(file)) - # There are 559 transparent pixels. + # There are 559 transparent pixels. im = im.convert('RGBA') assert_equal(im.split()[3].getcolors()[0][0], 559) @@ -255,7 +255,7 @@ def test_trns_p(): # Check writing a transparency of 0, issue #528 im = lena('P') im.info['transparency']=0 - + f = tempfile("temp.png") im.save(f) @@ -263,8 +263,8 @@ def test_trns_p(): assert_true('transparency' in im2.info) assert_image_equal(im2.convert('RGBA'), im.convert('RGBA')) - - + + def test_save_icc_profile_none(): # check saving files with an ICC profile set to None (omit profile) in_file = "Tests/images/icc_profile_none.png" diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 354eb972f..c759d8c0d 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -6,9 +6,9 @@ tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys())) def test_rt_metadata(): """ Test writing arbitray metadata into the tiff image directory Use case is ImageJ private tags, one numeric, one arbitrary - data. https://github.com/python-imaging/Pillow/issues/291 + data. https://github.com/python-pillow/Pillow/issues/291 """ - + img = lena() textdata = "This is some arbitrary metadata for a text field" @@ -20,15 +20,15 @@ def test_rt_metadata(): f = tempfile("temp.tif") img.save(f, tiffinfo=info) - + loaded = Image.open(f) assert_equal(loaded.tag[50838], (len(textdata),)) assert_equal(loaded.tag[50839], textdata) - + def test_read_metadata(): img = Image.open('Tests/images/lena_g4.tif') - + known = {'YResolution': ((1207959552, 16777216),), 'PlanarConfiguration': (1,), 'BitsPerSample': (1,), @@ -48,7 +48,7 @@ def test_read_metadata(): 'StripOffsets': (8,), 'Software': 'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} - # assert_equal is equivalent, but less helpful in telling what's wrong. + # assert_equal is equivalent, but less helpful in telling what's wrong. named = img.tag.named() for tag, value in named.items(): assert_equal(known[tag], value) @@ -70,11 +70,11 @@ def test_write_metadata(): reloaded = loaded.tag.named() ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] - + for tag, value in reloaded.items(): if tag not in ignored: assert_equal(original[tag], value, "%s didn't roundtrip" % tag) for tag, value in original.items(): - if tag not in ignored: + if tag not in ignored: assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 6a39b0e3b..ce7412e94 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -28,7 +28,7 @@ def test_default(): assert_image(im, "RGB", im.size) -# ref https://github.com/python-imaging/Pillow/issues/274 +# ref https://github.com/python-pillow/Pillow/issues/274 def _test_float_conversion(im): orig = im.getpixel((5, 5)) @@ -76,7 +76,7 @@ def test_trns_p(): assert_no_exception(lambda: rgb.save(f)) -# ref https://github.com/python-imaging/Pillow/issues/664 +# ref https://github.com/python-pillow/Pillow/issues/664 def test_trns_p_rgba(): # Arrange diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py index 67f5904a2..da3d8115e 100644 --- a/Tests/test_image_getpixel.py +++ b/Tests/test_image_getpixel.py @@ -16,27 +16,27 @@ def color(mode): def check(mode, c=None): if not c: c = color(mode) - + #check putpixel im = Image.new(mode, (1, 1), None) im.putpixel((0, 0), c) assert_equal(im.getpixel((0, 0)), c, "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c)) - + # check inital color im = Image.new(mode, (1, 1), c) assert_equal(im.getpixel((0, 0)), c, "initial color failed for mode %s, color %s " % (mode, color)) -def test_basic(): +def test_basic(): for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", "P", "PA", "RGB", "RGBA", "RGBX", "CMYK","YCbCr"): check(mode) def test_signedness(): - # see https://github.com/python-imaging/Pillow/issues/452 + # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* for mode in ("I;16", "I;16B"): check(mode, 2**15-1) @@ -44,6 +44,6 @@ def test_signedness(): check(mode, 2**15+1) check(mode, 2**16-1) - + diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 34233f80e..4fc11ca05 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -4,7 +4,7 @@ from PIL import Image if hasattr(sys, 'pypy_version_info'): # This takes _forever_ on pypy. Open Bug, - # see https://github.com/python-imaging/Pillow/issues/484 + # see https://github.com/python-pillow/Pillow/issues/484 skip() def test_sanity(): @@ -26,7 +26,7 @@ def test_sanity(): def test_16bit_lut(): """ Tests for 16 bit -> 8 bit lut for converting I->L images - see https://github.com/python-imaging/Pillow/issues/440 + see https://github.com/python-pillow/Pillow/issues/440 """ im = lena("I") diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index fdee6072f..dd9b6fe5c 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -10,9 +10,9 @@ def test_extent(): w//2,h//2), # ul -> lr Image.BILINEAR) - + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - + assert_image_similar(transformed, scaled, 10) # undone -- precision? def test_quad(): @@ -23,9 +23,9 @@ def test_quad(): (0,0,0,h//2, w//2,h//2,w//2,0), # ul -> ccw around quad Image.BILINEAR) - + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - + assert_image_equal(transformed, scaled) def test_mesh(): @@ -48,8 +48,8 @@ def test_mesh(): checker = Image.new('RGBA', im.size) checker.paste(scaled, (0,0)) checker.paste(scaled, (w//2,h//2)) - - assert_image_equal(transformed, checker) + + assert_image_equal(transformed, checker) # now, check to see that the extra area is (0,0,0,0) blank = Image.new('RGBA', (w//2,h//2), (0,0,0,0)) @@ -59,34 +59,34 @@ def test_mesh(): def _test_alpha_premult(op): # create image with half white, half black, with the black half transparent. - # do op, + # do op, # there should be no darkness in the white section. im = Image.new('RGBA', (10,10), (0,0,0,0)); im2 = Image.new('RGBA', (5,10), (255,255,255,255)); im.paste(im2, (0,0)) - + im = op(im, (40,10)) im_background = Image.new('RGB', (40,10), (255,255,255)) im_background.paste(im, (0,0), im) - + hist = im_background.histogram() assert_equal(40*10, hist[-1]) - + def test_alpha_premult_resize(): def op (im, sz): return im.resize(sz, Image.LINEAR) - + _test_alpha_premult(op) - + def test_alpha_premult_transform(): - + def op(im, sz): (w,h) = im.size return im.transform(sz, Image.EXTENT, (0,0, - w,h), + w,h), Image.BILINEAR) _test_alpha_premult(op) @@ -94,7 +94,7 @@ def test_alpha_premult_transform(): def test_blank_fill(): # attempting to hit - # https://github.com/python-imaging/Pillow/issues/254 reported + # https://github.com/python-pillow/Pillow/issues/254 reported # # issue is that transforms with transparent overflow area # contained junk from previous images, especially on systems with @@ -106,11 +106,11 @@ def test_blank_fill(): # # Running by default, but I'd totally understand not doing it in # the future - - foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) - for a in range(1,65)] - # Yeah. Watch some JIT optimize this out. + foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) + for a in range(1,65)] + + # Yeah. Watch some JIT optimize this out. foo = None test_mesh() diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index adf282b03..e3992828a 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -49,14 +49,14 @@ def test_parser(): if EpsImagePlugin.has_ghostscript(): im1, im2 = roundtrip("EPS") - assert_image_similar(im1, im2.convert('L'),20) # EPS comes back in RGB - + assert_image_similar(im1, im2.convert('L'),20) # EPS comes back in RGB + if "jpeg_encoder" in codecs: im1, im2 = roundtrip("JPEG") # lossy compression assert_image(im1, im2.mode, im2.size) # XXX Why assert exception and why does it fail? - # https://github.com/python-imaging/Pillow/issues/78 + # https://github.com/python-pillow/Pillow/issues/78 #assert_exception(IOError, lambda: roundtrip("PDF")) def test_ico(): diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 2683c561b..6b2b95201 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -3,7 +3,7 @@ from PIL import Image import locale -# ref https://github.com/python-imaging/Pillow/issues/272 +# ref https://github.com/python-pillow/Pillow/issues/272 ## on windows, in polish locale: ## import locale @@ -16,7 +16,7 @@ import locale ## 7 ## 160 -# one of string.whitespace is not freely convertable into ascii. +# one of string.whitespace is not freely convertable into ascii. path = "Images/lena.jpg" diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 4833cabb2..9b7881c23 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -83,12 +83,12 @@ def test_16bit(): def test_to_array(): def _to_array(mode, dtype): - img = lena(mode) + img = lena(mode) np_img = numpy.array(img) _test_img_equals_nparray(img, np_img) assert_equal(np_img.dtype, numpy.dtype(dtype)) - - + + modes = [("L", 'uint8'), ("I", 'int32'), ("F", 'float32'), @@ -101,20 +101,20 @@ def test_to_array(): ("I;16B", '>u2'), ("I;16L", ' If you've discovered a bug, you can - open an issue + open an issue on Github.

diff --git a/docs/about.rst b/docs/about.rst index e8c9356dc..817773610 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -11,8 +11,8 @@ The fork authors' goal is to foster active development of PIL through: - Regular releases to the `Python Package Index`_ - Solicitation for community contributions and involvement on `Image-SIG`_ -.. _Travis CI: https://travis-ci.org/python-imaging/Pillow -.. _GitHub: https://github.com/python-imaging/Pillow +.. _Travis CI: https://travis-ci.org/python-pillow/Pillow +.. _GitHub: https://github.com/python-pillow/Pillow .. _Python Package Index: https://pypi.python.org/pypi/Pillow .. _Image-SIG: http://mail.python.org/mailman/listinfo/image-sig @@ -60,6 +60,6 @@ announcement. So if you still want to support PIL, please .. _report issues here first: https://bitbucket.org/effbot/pil-2009-raclette/issues -.. _open the corresponding Pillow tickets here: https://github.com/python-imaging/Pillow/issues +.. _open the corresponding Pillow tickets here: https://github.com/python-pillow/Pillow/issues Please provide a link to the PIL ticket so we can track the issue(s) upstream. diff --git a/docs/index.rst b/docs/index.rst index 25e9f6b73..2eb845bff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,8 +4,8 @@ Pillow Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. -.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master - :target: https://travis-ci.org/python-imaging/Pillow +.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master + :target: https://travis-ci.org/python-pillow/Pillow :alt: Travis CI build status .. image:: https://pypip.in/v/Pillow/badge.png @@ -24,7 +24,7 @@ To start using Pillow, please read the :doc:`installation instructions `. You can get the source and contribute at -https://github.com/python-imaging/Pillow. You can download archives +https://github.com/python-pillow/Pillow. You can download archives and old versions from `PyPI `_. .. toctree:: @@ -42,7 +42,7 @@ Support Pillow! PIL needs you! Please help us maintain the Python Imaging Library here: -- `GitHub `_ +- `GitHub `_ - `Freenode `_ - `Image-SIG `_ diff --git a/setup.py b/setup.py index 50ca985e3..83defd82b 100644 --- a/setup.py +++ b/setup.py @@ -234,7 +234,7 @@ class pil_build_ext(build_ext): elif sys.platform.startswith("linux"): arch_tp = (plat.processor(), plat.architecture()[0]) if arch_tp == ("x86_64","32bit"): - # 32 bit build on 64 bit machine. + # 32 bit build on 64 bit machine. _add_directory(library_dirs, "/usr/lib/i386-linux-gnu") else: for platform_ in arch_tp: @@ -339,7 +339,7 @@ class pil_build_ext(build_ext): # on Windows, look for the OpenJPEG libraries in the location that # the official installed puts them if sys.platform == "win32": - _add_directory(library_dirs, + _add_directory(library_dirs, os.path.join(os.environ.get("ProgramFiles", ""), "OpenJPEG 2.0", "lib")) _add_directory(include_dirs, @@ -378,7 +378,7 @@ class pil_build_ext(build_ext): if _find_include_file(self, "openjpeg-2.0/openjpeg.h"): if _find_library_file(self, "openjp2"): feature.jpeg2000 = "openjp2" - + if feature.want('tiff'): if _find_library_file(self, "tiff"): feature.tiff = "tiff" @@ -660,7 +660,7 @@ setup( _read('CHANGES.rst')).decode('utf-8'), author='Alex Clark (fork author)', author_email='aclark@aclark.net', - url='http://python-imaging.github.io/', + url='http://python-pillow.github.io/', classifiers=[ "Development Status :: 6 - Mature", "Topic :: Multimedia :: Graphics", From dc667b1f19b5dd85f97ebd14c7497eaaa218c229 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 3 Jun 2014 09:01:57 -0400 Subject: [PATCH 060/488] Fix link --- PIL/OleFileIO-README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/OleFileIO-README.md b/PIL/OleFileIO-README.md index 4a4fdcbca..11a0e9053 100644 --- a/PIL/OleFileIO-README.md +++ b/PIL/OleFileIO-README.md @@ -7,7 +7,7 @@ This is an improved version of the OleFileIO module from [PIL](http://www.python As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules) -OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-imaging.github.io/), the friendly fork of PIL. +OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL. OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL. @@ -348,4 +348,4 @@ By obtaining, using, and/or copying this software and/or its associated document Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. -SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. From cd967680cb1314ecb45cc10ff246b5c6756eb93f Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 3 Jun 2014 18:04:27 +0300 Subject: [PATCH 061/488] Move dummy test to test/ and run with nosetests --- .travis.yml | 4 +++- {PIL => test}/tests.py | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename {PIL => test}/tests.py (100%) diff --git a/.travis.yml b/.travis.yml index 36dae5b7a..17a0b0363 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls" + - "pip install coveralls nose" # webp - pushd depends && ./install_webp.sh && popd @@ -35,6 +35,8 @@ script: - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi + - nosetests test/ + after_success: - coverage report - coveralls diff --git a/PIL/tests.py b/test/tests.py similarity index 100% rename from PIL/tests.py rename to test/tests.py From 5c8c777ea4ac1a47efa32e1ee96a13b531351676 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 3 Jun 2014 22:38:56 +0300 Subject: [PATCH 062/488] Start moving tests over to use unittest --- .travis.yml | 3 +- Tests/test_image_frombytes.py | 10 - Tests/test_image_getim.py | 14 -- Tests/test_image_tobytes.py | 7 - test/test_image_frombytes.py | 18 ++ test/test_image_getim.py | 19 ++ test/test_image_tobytes.py | 13 ++ test/tester.py | 393 ++++++++++++++++++++++++++++++++++ test/tests.py | 8 +- 9 files changed, 450 insertions(+), 35 deletions(-) delete mode 100644 Tests/test_image_frombytes.py delete mode 100644 Tests/test_image_getim.py delete mode 100644 Tests/test_image_tobytes.py create mode 100644 test/test_image_frombytes.py create mode 100644 test/test_image_getim.py create mode 100644 test/test_image_tobytes.py create mode 100644 test/tester.py diff --git a/.travis.yml b/.travis.yml index 17a0b0363..1b864eecf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,12 +30,13 @@ script: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then nosetests test/; fi # Cover the others - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* -m nose test/; fi - - nosetests test/ after_success: - coverage report diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py deleted file mode 100644 index aa157aa6a..000000000 --- a/Tests/test_image_frombytes.py +++ /dev/null @@ -1,10 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - im1 = lena() - im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) - - assert_image_equal(im1, im2) - diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py deleted file mode 100644 index 8d2f12fc2..000000000 --- a/Tests/test_image_getim.py +++ /dev/null @@ -1,14 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - - im = lena() - type_repr = repr(type(im.getim())) - - if py3: - assert_true("PyCapsule" in type_repr) - - assert_true(isinstance(im.im.id, int)) - diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py deleted file mode 100644 index d42399993..000000000 --- a/Tests/test_image_tobytes.py +++ /dev/null @@ -1,7 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - data = lena().tobytes() - assert_true(isinstance(data, bytes)) diff --git a/test/test_image_frombytes.py b/test/test_image_frombytes.py new file mode 100644 index 000000000..ee68b6722 --- /dev/null +++ b/test/test_image_frombytes.py @@ -0,0 +1,18 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImageFromBytes(PillowTestCase): + + def test_sanity(self): + im1 = lena() + im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) + + self.assert_image_equal(im1, im2) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_getim.py b/test/test_image_getim.py new file mode 100644 index 000000000..02744a529 --- /dev/null +++ b/test/test_image_getim.py @@ -0,0 +1,19 @@ +from tester import unittest, PillowTestCase, lena, py3 + + +class TestImageGetIm(PillowTestCase): + + def test_sanity(self): + im = lena() + type_repr = repr(type(im.getim())) + + if py3: + self.assertIn("PyCapsule", type_repr) + + self.assertIsInstance(im.im.id, int) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_tobytes.py b/test/test_image_tobytes.py new file mode 100644 index 000000000..ee7b4c9e6 --- /dev/null +++ b/test/test_image_tobytes.py @@ -0,0 +1,13 @@ +from tester import unittest, lena + + +class TestImageToBytes(unittest.TestCase): + + def test_sanity(self): + data = lena().tobytes() + self.assertTrue(isinstance(data, bytes)) + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/tester.py b/test/tester.py new file mode 100644 index 000000000..77b38a15a --- /dev/null +++ b/test/tester.py @@ -0,0 +1,393 @@ +""" +Helper functions. +""" +from __future__ import print_function +import sys + +if sys.version_info[:2] <= (2, 6): + import unittest2 as unittest +else: + import unittest + + +class PillowTestCase(unittest.TestCase): + + def assert_image_equal(self, a, b, msg=None): + self.assertEqual( + a.mode, b.mode, + msg or "got mode %r, expected %r" % (a.mode, b.mode)) + self.assertEqual( + a.size, b.size, + msg or "got size %r, expected %r" % (a.size, b.size)) + self.assertEqual( + a.tobytes(), b.tobytes(), + msg or "got different content") + + +# # require that deprecation warnings are triggered +# import warnings +# warnings.simplefilter('default') +# # temporarily turn off resource warnings that warn about unclosed +# # files in the test scripts. +# try: +# warnings.filterwarnings("ignore", category=ResourceWarning) +# except NameError: +# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. +# pass + +import sys +py3 = (sys.version_info >= (3, 0)) + +# # some test helpers +# +# _target = None +# _tempfiles = [] +# _logfile = None +# +# +# def success(): +# import sys +# success.count += 1 +# if _logfile: +# print(sys.argv[0], success.count, failure.count, file=_logfile) +# return True +# +# +# def failure(msg=None, frame=None): +# import sys +# import linecache +# failure.count += 1 +# if _target: +# if frame is None: +# frame = sys._getframe() +# while frame.f_globals.get("__name__") != _target.__name__: +# frame = frame.f_back +# location = (frame.f_code.co_filename, frame.f_lineno) +# prefix = "%s:%d: " % location +# line = linecache.getline(*location) +# print(prefix + line.strip() + " failed:") +# if msg: +# print("- " + msg) +# if _logfile: +# print(sys.argv[0], success.count, failure.count, file=_logfile) +# return False +# +# success.count = failure.count = 0 +# +# +# # predicates +# +# def assert_almost_equal(a, b, msg=None, eps=1e-6): +# if abs(a-b) < eps: +# success() +# else: +# failure(msg or "got %r, expected %r" % (a, b)) +# +# +# def assert_deep_equal(a, b, msg=None): +# try: +# if len(a) == len(b): +# if all([x == y for x, y in zip(a, b)]): +# success() +# else: +# failure(msg or "got %s, expected %s" % (a, b)) +# else: +# failure(msg or "got length %s, expected %s" % (len(a), len(b))) +# except: +# assert_equal(a, b, msg) +# +# +# def assert_greater(a, b, msg=None): +# if a > b: +# success() +# else: +# failure(msg or "%r unexpectedly not greater than %r" % (a, b)) +# +# +# def assert_greater_equal(a, b, msg=None): +# if a >= b: +# success() +# else: +# failure( +# msg or "%r unexpectedly not greater than or equal to %r" +# % (a, b)) +# +# +# def assert_less(a, b, msg=None): +# if a < b: +# success() +# else: +# failure(msg or "%r unexpectedly not less than %r" % (a, b)) +# +# +# def assert_less_equal(a, b, msg=None): +# if a <= b: +# success() +# else: +# failure( +# msg or "%r unexpectedly not less than or equal to %r" % (a, b)) +# +# +# def assert_is_instance(a, b, msg=None): +# if isinstance(a, b): +# success() +# else: +# failure(msg or "got %r, expected %r" % (type(a), b)) +# +# +# def assert_in(a, b, msg=None): +# if a in b: +# success() +# else: +# failure(msg or "%r unexpectedly not in %r" % (a, b)) +# +# +# def assert_match(v, pattern, msg=None): +# import re +# if re.match(pattern, v): +# success() +# else: +# failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) +# +# +# def assert_exception(exc_class, func): +# import sys +# import traceback +# try: +# func() +# except exc_class: +# success() +# except: +# failure("expected %r exception, got %r" % ( +# exc_class.__name__, sys.exc_info()[0].__name__)) +# traceback.print_exc() +# else: +# failure( +# "expected %r exception, got no exception" % exc_class.__name__) +# +# +# def assert_no_exception(func): +# import sys +# import traceback +# try: +# func() +# except: +# failure("expected no exception, got %r" % sys.exc_info()[0].__name__) +# traceback.print_exc() +# else: +# success() +# +# +# def assert_warning(warn_class, func): +# # note: this assert calls func three times! +# import warnings +# +# def warn_error(message, category=UserWarning, **options): +# raise category(message) +# +# def warn_ignore(message, category=UserWarning, **options): +# pass +# warn = warnings.warn +# result = None +# try: +# warnings.warn = warn_ignore +# assert_no_exception(func) +# result = func() +# warnings.warn = warn_error +# assert_exception(warn_class, func) +# finally: +# warnings.warn = warn # restore +# return result +# +# # helpers +# +# from io import BytesIO +# +# +# def fromstring(data): +# from PIL import Image +# return Image.open(BytesIO(data)) +# +# +# def tostring(im, format, **options): +# out = BytesIO() +# im.save(out, format, **options) +# return out.getvalue() + + +def lena(mode="RGB", cache={}): + from PIL import Image + im = cache.get(mode) + if im is None: + if mode == "RGB": + im = Image.open("Images/lena.ppm") + elif mode == "F": + im = lena("L").convert(mode) + elif mode[:4] == "I;16": + im = lena("I").convert(mode) + else: + im = lena("RGB").convert(mode) + cache[mode] = im + return im + + +# def assert_image(im, mode, size, msg=None): +# if mode is not None and im.mode != mode: +# failure(msg or "got mode %r, expected %r" % (im.mode, mode)) +# elif size is not None and im.size != size: +# failure(msg or "got size %r, expected %r" % (im.size, size)) +# else: +# success() +# +# +# def assert_image_equal(a, b, msg=None): +# if a.mode != b.mode: +# failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) +# elif a.size != b.size: +# failure(msg or "got size %r, expected %r" % (a.size, b.size)) +# elif a.tobytes() != b.tobytes(): +# failure(msg or "got different content") +# else: +# success() +# +# +# def assert_image_completely_equal(a, b, msg=None): +# if a != b: +# failure(msg or "images different") +# else: +# success() +# +# +# def assert_image_similar(a, b, epsilon, msg=None): +# epsilon = float(epsilon) +# if a.mode != b.mode: +# return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) +# elif a.size != b.size: +# return failure(msg or "got size %r, expected %r" % (a.size, b.size)) +# diff = 0 +# try: +# ord(b'0') +# for abyte, bbyte in zip(a.tobytes(), b.tobytes()): +# diff += abs(ord(abyte)-ord(bbyte)) +# except: +# for abyte, bbyte in zip(a.tobytes(), b.tobytes()): +# diff += abs(abyte-bbyte) +# ave_diff = float(diff)/(a.size[0]*a.size[1]) +# if epsilon < ave_diff: +# return failure( +# msg or "average pixel value difference %.4f > epsilon %.4f" % ( +# ave_diff, epsilon)) +# else: +# return success() +# +# +# def tempfile(template, *extra): +# import os +# import os.path +# import sys +# import tempfile +# files = [] +# root = os.path.join(tempfile.gettempdir(), 'pillow-tests') +# try: +# os.mkdir(root) +# except OSError: +# pass +# for temp in (template,) + extra: +# assert temp[:5] in ("temp.", "temp_") +# name = os.path.basename(sys.argv[0]) +# name = temp[:4] + os.path.splitext(name)[0][4:] +# name = name + "_%d" % len(_tempfiles) + temp[4:] +# name = os.path.join(root, name) +# files.append(name) +# _tempfiles.extend(files) +# return files[0] +# +# +# # test runner +# +# def run(): +# global _target, _tests, run +# import sys +# import traceback +# _target = sys.modules["__main__"] +# run = None # no need to run twice +# tests = [] +# for name, value in list(vars(_target).items()): +# if name[:5] == "test_" and type(value) is type(success): +# tests.append((value.__code__.co_firstlineno, name, value)) +# tests.sort() # sort by line +# for lineno, name, func in tests: +# try: +# _tests = [] +# func() +# for func, args in _tests: +# func(*args) +# except: +# t, v, tb = sys.exc_info() +# tb = tb.tb_next +# if tb: +# failure(frame=tb.tb_frame) +# traceback.print_exception(t, v, tb) +# else: +# print("%s:%d: cannot call test function: %s" % ( +# sys.argv[0], lineno, v)) +# failure.count += 1 +# +# +# def yield_test(function, *args): +# # collect delayed/generated tests +# _tests.append((function, args)) +# +# +# def skip(msg=None): +# import os +# print("skip") +# os._exit(0) # don't run exit handlers +# +# +# def ignore(pattern): +# """Tells the driver to ignore messages matching the pattern, for the +# duration of the current test.""" +# print('ignore: %s' % pattern) +# +# +# def _setup(): +# global _logfile +# +# import sys +# if "--coverage" in sys.argv: +# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) +# with warnings.catch_warnings(): +# warnings.simplefilter("ignore") +# import coverage +# cov = coverage.coverage(auto_data=True, include="PIL/*") +# cov.start() +# +# def report(): +# if run: +# run() +# if success.count and not failure.count: +# print("ok") +# # only clean out tempfiles if test passed +# import os +# import os.path +# import tempfile +# for file in _tempfiles: +# try: +# os.remove(file) +# except OSError: +# pass # report? +# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') +# try: +# os.rmdir(temp_root) +# except OSError: +# pass +# +# import atexit +# atexit.register(report) +# +# if "--log" in sys.argv: +# _logfile = open("test.log", "a") +# +# +# _setup() diff --git a/test/tests.py b/test/tests.py index eb4a8342d..1bd308922 100644 --- a/test/tests.py +++ b/test/tests.py @@ -1,7 +1,7 @@ -import unittest +from tester import unittest -class PillowTests(unittest.TestCase): +class SomeTests(unittest.TestCase): """ Can we start moving the test suite here? """ @@ -10,8 +10,10 @@ class PillowTests(unittest.TestCase): """ Great idea! """ - assert True is True + self.assertTrue(True) if __name__ == '__main__': unittest.main() + +# End of file From 2bde38d8f5a7e4040ddd8d2f5760d927e24a0bd1 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 3 Jun 2014 23:22:02 +0300 Subject: [PATCH 063/488] Move more tests; add more helpers; install unittest2 for Python 2.7 --- .travis.yml | 1 + Tests/test_imagedraw.py | 265 ---------------------------------------- test/test_imagedraw.py | 253 ++++++++++++++++++++++++++++++++++++++ test/tester.py | 15 +++ 4 files changed, 269 insertions(+), 265 deletions(-) delete mode 100644 Tests/test_imagedraw.py create mode 100644 test/test_imagedraw.py diff --git a/.travis.yml b/.travis.yml index 1b864eecf..68e985b9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - "pip install coveralls nose" + - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp - pushd depends && ./install_webp.sh && popd diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py deleted file mode 100644 index c47638a05..000000000 --- a/Tests/test_imagedraw.py +++ /dev/null @@ -1,265 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageColor -from PIL import ImageDraw - -# Image size -w, h = 100, 100 - -# Bounding box points -x0 = int(w / 4) -x1 = int(x0 * 3) -y0 = int(h / 4) -y1 = int(x0 * 3) - -# Two kinds of bounding box -bbox1 = [(x0, y0), (x1, y1)] -bbox2 = [x0, y0, x1, y1] - -# Two kinds of coordinate sequences -points1 = [(10, 10), (20, 40), (30, 30)] -points2 = [10, 10, 20, 40, 30, 30] - - -def test_sanity(): - - im = lena("RGB").copy() - - draw = ImageDraw.ImageDraw(im) - draw = ImageDraw.Draw(im) - - draw.ellipse(list(range(4))) - draw.line(list(range(10))) - draw.polygon(list(range(100))) - draw.rectangle(list(range(4))) - - success() - - -def test_deprecated(): - - im = lena().copy() - - draw = ImageDraw.Draw(im) - - assert_warning(DeprecationWarning, lambda: draw.setink(0)) - assert_warning(DeprecationWarning, lambda: draw.setfill(0)) - - -def helper_arc(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - # FIXME Fill param should be named outline. - draw.arc(bbox, 0, 180) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_arc.png")) - - -def test_arc1(): - helper_arc(bbox1) - - -def test_arc2(): - helper_arc(bbox2) - - -def test_bitmap(): - # Arrange - small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.bitmap((10, 10), small) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) - - -def helper_chord(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.chord(bbox, 0, 180, fill="red", outline="yellow") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_chord.png")) - - -def test_chord1(): - helper_chord(bbox1) - - -def test_chord2(): - helper_chord(bbox2) - - -def helper_ellipse(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.ellipse(bbox, fill="green", outline="blue") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_ellipse.png")) - - -def test_ellipse1(): - helper_ellipse(bbox1) - - -def test_ellipse2(): - helper_ellipse(bbox2) - - -def helper_line(points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.line(points1, fill="yellow", width=2) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) - - -def test_line1(): - helper_line(points1) - - -def test_line2(): - helper_line(points2) - - -def helper_pieslice(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.pieslice(bbox, -90, 45, fill="white", outline="blue") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) - - -def test_pieslice1(): - helper_pieslice(bbox1) - - -def test_pieslice2(): - helper_pieslice(bbox2) - - -def helper_point(points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.point(points1, fill="yellow") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) - - -def test_point1(): - helper_point(points1) - - -def test_point2(): - helper_point(points2) - - -def helper_polygon(points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.polygon(points1, fill="red", outline="blue") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) - - -def test_polygon1(): - helper_polygon(points1) - - -def test_polygon2(): - helper_polygon(points2) - - -def helper_rectangle(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.rectangle(bbox, fill="black", outline="green") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) - - -def test_rectangle1(): - helper_rectangle(bbox1) - - -def test_rectangle2(): - helper_rectangle(bbox2) - - -def test_floodfill(): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) - - # Act - ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) - - -def test_floodfill_border(): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) - - # Act - ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), - border=ImageColor.getrgb("black")) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) - - -# End of file diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py new file mode 100644 index 000000000..60e9b6f03 --- /dev/null +++ b/test/test_imagedraw.py @@ -0,0 +1,253 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image + +from PIL import Image +from PIL import ImageColor +from PIL import ImageDraw + +# Image size +w, h = 100, 100 + +# Bounding box points +x0 = int(w / 4) +x1 = int(x0 * 3) +y0 = int(h / 4) +y1 = int(x0 * 3) + +# Two kinds of bounding box +bbox1 = [(x0, y0), (x1, y1)] +bbox2 = [x0, y0, x1, y1] + +# Two kinds of coordinate sequences +points1 = [(10, 10), (20, 40), (30, 30)] +points2 = [10, 10, 20, 40, 30, 30] + + +class TestImageDraw(PillowTestCase): + + def test_sanity(self): + im = lena("RGB").copy() + + draw = ImageDraw.ImageDraw(im) + draw = ImageDraw.Draw(im) + + draw.ellipse(list(range(4))) + draw.line(list(range(10))) + draw.polygon(list(range(100))) + draw.rectangle(list(range(4))) + + def test_deprecated(self): + im = lena().copy() + + draw = ImageDraw.Draw(im) + + self.assert_warning(DeprecationWarning, lambda: draw.setink(0)) + self.assert_warning(DeprecationWarning, lambda: draw.setfill(0)) + + def helper_arc(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + # FIXME Fill param should be named outline. + draw.arc(bbox, 0, 180) + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_arc.png")) + + def test_arc1(self): + self.helper_arc(bbox1) + + def test_arc2(self): + self.helper_arc(bbox2) + + def test_bitmap(self): + # Arrange + small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.bitmap((10, 10), small) + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_bitmap.png")) + + def helper_chord(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.chord(bbox, 0, 180, fill="red", outline="yellow") + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_chord.png")) + + def test_chord1(self): + self.helper_chord(bbox1) + + def test_chord2(self): + self.helper_chord(bbox2) + + def helper_ellipse(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.ellipse(bbox, fill="green", outline="blue") + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_ellipse.png")) + + def test_ellipse1(self): + self.helper_ellipse(bbox1) + + def test_ellipse2(self): + self.helper_ellipse(bbox2) + + def helper_line(self, points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.line(points1, fill="yellow", width=2) + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_line.png")) + + def test_line1(self): + self.helper_line(points1) + + def test_line2(self): + self.helper_line(points2) + + def helper_pieslice(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.pieslice(bbox, -90, 45, fill="white", outline="blue") + del draw + + # Assert + self.assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) + + def test_pieslice1(self): + self.helper_pieslice(bbox1) + + def test_pieslice2(self): + self.helper_pieslice(bbox2) + + def helper_point(self, points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.point(points1, fill="yellow") + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_point.png")) + + def test_point1(self): + self.helper_point(points1) + + + def test_point2(self): + self.helper_point(points2) + + def helper_polygon(self, points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.polygon(points1, fill="red", outline="blue") + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_polygon.png")) + + def test_polygon1(self): + self.helper_polygon(points1) + + + def test_polygon2(self): + self.helper_polygon(points2) + + + def helper_rectangle(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(bbox, fill="black", outline="green") + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_rectangle.png")) + + def test_rectangle1(self): + self.helper_rectangle(bbox1) + + def test_rectangle2(self): + self.helper_rectangle(bbox2) + + def test_floodfill(self): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_floodfill.png")) + + def test_floodfill_border(self): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill( + im, centre_point, ImageColor.getrgb("red"), + border=ImageColor.getrgb("black")) + del draw + + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_floodfill2.png")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/tester.py b/test/tester.py index 77b38a15a..894d37883 100644 --- a/test/tester.py +++ b/test/tester.py @@ -23,6 +23,21 @@ class PillowTestCase(unittest.TestCase): a.tobytes(), b.tobytes(), msg or "got different content") + def assert_warning(self, warn_class, func): + import warnings + + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Hopefully trigger a warning. + func() + + # Verify some things. + self.assertEqual(len(w), 1) + assert issubclass(w[-1].category, warn_class) + self.assertIn("deprecated", str(w[-1].message)) + # # require that deprecation warnings are triggered # import warnings From 682ad75759548f0e3cd590fcfb14f396701061e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Tue, 3 Jun 2014 16:34:23 -0400 Subject: [PATCH 064/488] DOC: fix name in docstring --- PIL/Image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 887ceabc1..e064ed9ef 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1529,7 +1529,7 @@ class Image: clockwise around its centre. :param angle: In degrees counter clockwise. - :param filter: An optional resampling filter. This can be + :param resample: An optional resampling filter. This can be one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 environment), or :py:attr:`PIL.Image.BICUBIC` @@ -1550,7 +1550,6 @@ class Image: math.cos(angle), math.sin(angle), 0.0, -math.sin(angle), math.cos(angle), 0.0 ] - def transform(x, y, matrix=matrix): (a, b, c, d, e, f) = matrix From d082c58043423e5eb510151ce394c003a92775e1 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 3 Jun 2014 23:35:21 +0300 Subject: [PATCH 065/488] pep8/pyflakes --- .travis.yml | 2 ++ test/test_imagedraw.py | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68e985b9a..4a2250771 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,5 +45,7 @@ after_success: - pip install pep8 pyflakes - pep8 PIL/*.py - pyflakes PIL/*.py + - pep8 test/*.py + - pyflakes test/*.py - pep8 Tests/*.py - pyflakes Tests/*.py diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py index 60e9b6f03..f9a5e5f6a 100644 --- a/test/test_imagedraw.py +++ b/test/test_imagedraw.py @@ -1,7 +1,5 @@ from tester import unittest, PillowTestCase, lena -from PIL import Image - from PIL import Image from PIL import ImageColor from PIL import ImageDraw @@ -146,7 +144,8 @@ class TestImageDraw(PillowTestCase): del draw # Assert - self.assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_pieslice.png")) def test_pieslice1(self): self.helper_pieslice(bbox1) @@ -170,7 +169,6 @@ class TestImageDraw(PillowTestCase): def test_point1(self): self.helper_point(points1) - def test_point2(self): self.helper_point(points2) @@ -190,11 +188,9 @@ class TestImageDraw(PillowTestCase): def test_polygon1(self): self.helper_polygon(points1) - def test_polygon2(self): self.helper_polygon(points2) - def helper_rectangle(self, bbox): # Arrange im = Image.new("RGB", (w, h)) From 9497525abcedc7664b5de82f1fad2b8e1294bbad Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 3 Jun 2014 16:39:17 -0400 Subject: [PATCH 066/488] Fix coveralls.io URLs --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 33b9813d0..0831a6b81 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt :target: https://pypi.python.org/pypi/Pillow/ :alt: Number of PyPI downloads -.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master - :target: https://coveralls.io/r/python-imaging/Pillow?branch=master +.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master + :target: https://coveralls.io/r/python-pillow/Pillow?branch=master The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more. From 2e52f3ab46d98b7978f3d145c20dc38fbd84d90f Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 4 Jun 2014 00:05:48 +0300 Subject: [PATCH 067/488] Move more tests; remove some redundant helpers --- Tests/test_000_sanity.py | 24 --------- Tests/test_image_histogram.py | 19 ------- Tests/test_imageenhance.py | 19 ------- Tests/test_lib_image.py | 30 ----------- test/test_000_sanity.py | 32 ++++++++++++ test/test_image_histogram.py | 26 ++++++++++ test/test_imageenhance.py | 28 +++++++++++ test/test_lib_image.py | 39 +++++++++++++++ test/tester.py | 94 ----------------------------------- 9 files changed, 125 insertions(+), 186 deletions(-) delete mode 100644 Tests/test_000_sanity.py delete mode 100644 Tests/test_image_histogram.py delete mode 100644 Tests/test_imageenhance.py delete mode 100644 Tests/test_lib_image.py create mode 100644 test/test_000_sanity.py create mode 100644 test/test_image_histogram.py create mode 100644 test/test_imageenhance.py create mode 100644 test/test_lib_image.py diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py deleted file mode 100644 index a30786458..000000000 --- a/Tests/test_000_sanity.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import print_function -from tester import * - -import PIL -import PIL.Image - -# Make sure we have the binary extension -im = PIL.Image.core.new("L", (100, 100)) - -assert PIL.Image.VERSION[:3] == '1.1' - -# Create an image and do stuff with it. -im = PIL.Image.new("1", (100, 100)) -assert (im.mode, im.size) == ('1', (100, 100)) -assert len(im.tobytes()) == 1300 - -# Create images in all remaining major modes. -im = PIL.Image.new("L", (100, 100)) -im = PIL.Image.new("P", (100, 100)) -im = PIL.Image.new("RGB", (100, 100)) -im = PIL.Image.new("I", (100, 100)) -im = PIL.Image.new("F", (100, 100)) - -print("ok") diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py deleted file mode 100644 index c86cb578a..000000000 --- a/Tests/test_image_histogram.py +++ /dev/null @@ -1,19 +0,0 @@ -from tester import * - -from PIL import Image - -def test_histogram(): - - def histogram(mode): - h = lena(mode).histogram() - return len(h), min(h), max(h) - - assert_equal(histogram("1"), (256, 0, 8872)) - assert_equal(histogram("L"), (256, 0, 199)) - assert_equal(histogram("I"), (256, 0, 199)) - assert_equal(histogram("F"), (256, 0, 199)) - assert_equal(histogram("P"), (256, 0, 2912)) - assert_equal(histogram("RGB"), (768, 0, 285)) - assert_equal(histogram("RGBA"), (1024, 0, 16384)) - assert_equal(histogram("CMYK"), (1024, 0, 16384)) - assert_equal(histogram("YCbCr"), (768, 0, 741)) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py deleted file mode 100644 index 04f16bfa5..000000000 --- a/Tests/test_imageenhance.py +++ /dev/null @@ -1,19 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageEnhance - -def test_sanity(): - - # FIXME: assert_image - assert_no_exception(lambda: ImageEnhance.Color(lena()).enhance(0.5)) - assert_no_exception(lambda: ImageEnhance.Contrast(lena()).enhance(0.5)) - assert_no_exception(lambda: ImageEnhance.Brightness(lena()).enhance(0.5)) - assert_no_exception(lambda: ImageEnhance.Sharpness(lena()).enhance(0.5)) - -def test_crash(): - - # crashes on small images - im = Image.new("RGB", (1, 1)) - assert_no_exception(lambda: ImageEnhance.Sharpness(im).enhance(0.5)) - diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py deleted file mode 100644 index 93aa694d8..000000000 --- a/Tests/test_lib_image.py +++ /dev/null @@ -1,30 +0,0 @@ -from tester import * - -from PIL import Image - -def test_setmode(): - - im = Image.new("L", (1, 1), 255) - im.im.setmode("1") - assert_equal(im.im.getpixel((0, 0)), 255) - im.im.setmode("L") - assert_equal(im.im.getpixel((0, 0)), 255) - - im = Image.new("1", (1, 1), 1) - im.im.setmode("L") - assert_equal(im.im.getpixel((0, 0)), 255) - im.im.setmode("1") - assert_equal(im.im.getpixel((0, 0)), 255) - - im = Image.new("RGB", (1, 1), (1, 2, 3)) - im.im.setmode("RGB") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3)) - im.im.setmode("RGBA") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3, 255)) - im.im.setmode("RGBX") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3, 255)) - im.im.setmode("RGB") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3)) - - assert_exception(ValueError, lambda: im.im.setmode("L")) - assert_exception(ValueError, lambda: im.im.setmode("RGBABCDE")) diff --git a/test/test_000_sanity.py b/test/test_000_sanity.py new file mode 100644 index 000000000..b536dd96a --- /dev/null +++ b/test/test_000_sanity.py @@ -0,0 +1,32 @@ +from tester import unittest, PillowTestCase + +import PIL +import PIL.Image + + +class TestSanity(PillowTestCase): + + def test_sanity(self): + + # Make sure we have the binary extension + im = PIL.Image.core.new("L", (100, 100)) + + self.assertEqual(PIL.Image.VERSION[:3], '1.1') + + # Create an image and do stuff with it. + im = PIL.Image.new("1", (100, 100)) + self.assertEqual((im.mode, im.size), ('1', (100, 100))) + self.assertEqual(len(im.tobytes()), 1300) + + # Create images in all remaining major modes. + im = PIL.Image.new("L", (100, 100)) + im = PIL.Image.new("P", (100, 100)) + im = PIL.Image.new("RGB", (100, 100)) + im = PIL.Image.new("I", (100, 100)) + im = PIL.Image.new("F", (100, 100)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_histogram.py b/test/test_image_histogram.py new file mode 100644 index 000000000..8a46149ff --- /dev/null +++ b/test/test_image_histogram.py @@ -0,0 +1,26 @@ +from tester import unittest, PillowTestCase, lena + + +class TestImageHistogram(PillowTestCase): + + def test_histogram(self): + + def histogram(mode): + h = lena(mode).histogram() + return len(h), min(h), max(h) + + self.assertEqual(histogram("1"), (256, 0, 8872)) + self.assertEqual(histogram("L"), (256, 0, 199)) + self.assertEqual(histogram("I"), (256, 0, 199)) + self.assertEqual(histogram("F"), (256, 0, 199)) + self.assertEqual(histogram("P"), (256, 0, 2912)) + self.assertEqual(histogram("RGB"), (768, 0, 285)) + self.assertEqual(histogram("RGBA"), (1024, 0, 16384)) + self.assertEqual(histogram("CMYK"), (1024, 0, 16384)) + self.assertEqual(histogram("YCbCr"), (768, 0, 741)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imageenhance.py b/test/test_imageenhance.py new file mode 100644 index 000000000..c126bf344 --- /dev/null +++ b/test/test_imageenhance.py @@ -0,0 +1,28 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image +from PIL import ImageEnhance + + +class TestImageEnhance(PillowTestCase): + + def test_sanity(self): + + # FIXME: assert_image + # Implicit asserts no exception: + ImageEnhance.Color(lena()).enhance(0.5) + ImageEnhance.Contrast(lena()).enhance(0.5) + ImageEnhance.Brightness(lena()).enhance(0.5) + ImageEnhance.Sharpness(lena()).enhance(0.5) + + def test_crash(self): + + # crashes on small images + im = Image.new("RGB", (1, 1)) + ImageEnhance.Sharpness(im).enhance(0.5) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_lib_image.py b/test/test_lib_image.py new file mode 100644 index 000000000..8694c1d51 --- /dev/null +++ b/test/test_lib_image.py @@ -0,0 +1,39 @@ +from tester import unittest, PillowTestCase + +from PIL import Image + + +class TestSanity(PillowTestCase): + + def test_setmode(self): + + im = Image.new("L", (1, 1), 255) + im.im.setmode("1") + self.assertEqual(im.im.getpixel((0, 0)), 255) + im.im.setmode("L") + self.assertEqual(im.im.getpixel((0, 0)), 255) + + im = Image.new("1", (1, 1), 1) + im.im.setmode("L") + self.assertEqual(im.im.getpixel((0, 0)), 255) + im.im.setmode("1") + self.assertEqual(im.im.getpixel((0, 0)), 255) + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + im.im.setmode("RGB") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3)) + im.im.setmode("RGBA") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3, 255)) + im.im.setmode("RGBX") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3, 255)) + im.im.setmode("RGB") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3)) + + self.assertRaises(ValueError, lambda: im.im.setmode("L")) + self.assertRaises(ValueError, lambda: im.im.setmode("RGBABCDE")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/tester.py b/test/tester.py index 894d37883..8df32e317 100644 --- a/test/tester.py +++ b/test/tester.py @@ -112,51 +112,6 @@ py3 = (sys.version_info >= (3, 0)) # assert_equal(a, b, msg) # # -# def assert_greater(a, b, msg=None): -# if a > b: -# success() -# else: -# failure(msg or "%r unexpectedly not greater than %r" % (a, b)) -# -# -# def assert_greater_equal(a, b, msg=None): -# if a >= b: -# success() -# else: -# failure( -# msg or "%r unexpectedly not greater than or equal to %r" -# % (a, b)) -# -# -# def assert_less(a, b, msg=None): -# if a < b: -# success() -# else: -# failure(msg or "%r unexpectedly not less than %r" % (a, b)) -# -# -# def assert_less_equal(a, b, msg=None): -# if a <= b: -# success() -# else: -# failure( -# msg or "%r unexpectedly not less than or equal to %r" % (a, b)) -# -# -# def assert_is_instance(a, b, msg=None): -# if isinstance(a, b): -# success() -# else: -# failure(msg or "got %r, expected %r" % (type(a), b)) -# -# -# def assert_in(a, b, msg=None): -# if a in b: -# success() -# else: -# failure(msg or "%r unexpectedly not in %r" % (a, b)) -# -# # def assert_match(v, pattern, msg=None): # import re # if re.match(pattern, v): @@ -165,55 +120,6 @@ py3 = (sys.version_info >= (3, 0)) # failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) # # -# def assert_exception(exc_class, func): -# import sys -# import traceback -# try: -# func() -# except exc_class: -# success() -# except: -# failure("expected %r exception, got %r" % ( -# exc_class.__name__, sys.exc_info()[0].__name__)) -# traceback.print_exc() -# else: -# failure( -# "expected %r exception, got no exception" % exc_class.__name__) -# -# -# def assert_no_exception(func): -# import sys -# import traceback -# try: -# func() -# except: -# failure("expected no exception, got %r" % sys.exc_info()[0].__name__) -# traceback.print_exc() -# else: -# success() -# -# -# def assert_warning(warn_class, func): -# # note: this assert calls func three times! -# import warnings -# -# def warn_error(message, category=UserWarning, **options): -# raise category(message) -# -# def warn_ignore(message, category=UserWarning, **options): -# pass -# warn = warnings.warn -# result = None -# try: -# warnings.warn = warn_ignore -# assert_no_exception(func) -# result = func() -# warnings.warn = warn_error -# assert_exception(warn_class, func) -# finally: -# warnings.warn = warn # restore -# return result -# # # helpers # # from io import BytesIO From e518879c0ea37be3afe64ac4d8817e4267b551a6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jun 2014 00:11:31 +0300 Subject: [PATCH 068/488] Fix Coveralls link --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2eb845bff..a8c204228 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,8 +16,8 @@ Python Imaging Library by Fredrik Lundh and Contributors. :target: https://pypi.python.org/pypi/Pillow/ :alt: Number of PyPI downloads -.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master - :target: https://coveralls.io/r/python-imaging/Pillow?branch=master +.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master + :target: https://coveralls.io/r/python-pillow/Pillow?branch=master :alt: Test coverage To start using Pillow, please read the :doc:`installation From c8626c6e93d1d05c1d25e649447b4a3d3b62ee8e Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 4 Jun 2014 09:09:23 +0300 Subject: [PATCH 069/488] Move more tests --- Tests/test_image_array.py | 33 -------------- Tests/test_image_crop.py | 52 --------------------- Tests/test_image_filter.py | 82 --------------------------------- Tests/test_image_putdata.py | 40 ---------------- Tests/test_imagefilter.py | 31 ------------- Tests/test_imagestat.py | 52 --------------------- test/test_image_array.py | 46 +++++++++++++++++++ test/test_image_crop.py | 59 ++++++++++++++++++++++++ test/test_image_filter.py | 91 +++++++++++++++++++++++++++++++++++++ test/test_image_putdata.py | 48 +++++++++++++++++++ test/test_imagefilter.py | 37 +++++++++++++++ test/test_imagestat.py | 63 +++++++++++++++++++++++++ test/tester.py | 11 ----- test/tests.py | 19 -------- 14 files changed, 344 insertions(+), 320 deletions(-) delete mode 100644 Tests/test_image_array.py delete mode 100644 Tests/test_image_crop.py delete mode 100644 Tests/test_image_filter.py delete mode 100644 Tests/test_image_putdata.py delete mode 100644 Tests/test_imagefilter.py delete mode 100644 Tests/test_imagestat.py create mode 100644 test/test_image_array.py create mode 100644 test/test_image_crop.py create mode 100644 test/test_image_filter.py create mode 100644 test/test_image_putdata.py create mode 100644 test/test_imagefilter.py create mode 100644 test/test_imagestat.py delete mode 100644 test/tests.py diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py deleted file mode 100644 index 351621d3a..000000000 --- a/Tests/test_image_array.py +++ /dev/null @@ -1,33 +0,0 @@ -from tester import * - -from PIL import Image - -im = lena().resize((128, 100)) - -def test_toarray(): - def test(mode): - ai = im.convert(mode).__array_interface__ - return ai["shape"], ai["typestr"], len(ai["data"]) - # assert_equal(test("1"), ((100, 128), '|b1', 1600)) - assert_equal(test("L"), ((100, 128), '|u1', 12800)) - assert_equal(test("I"), ((100, 128), Image._ENDIAN + 'i4', 51200)) # FIXME: wrong? - assert_equal(test("F"), ((100, 128), Image._ENDIAN + 'f4', 51200)) # FIXME: wrong? - assert_equal(test("RGB"), ((100, 128, 3), '|u1', 38400)) - assert_equal(test("RGBA"), ((100, 128, 4), '|u1', 51200)) - assert_equal(test("RGBX"), ((100, 128, 4), '|u1', 51200)) - -def test_fromarray(): - def test(mode): - i = im.convert(mode) - a = i.__array_interface__ - a["strides"] = 1 # pretend it's non-contigous - i.__array_interface__ = a # patch in new version of attribute - out = Image.fromarray(i) - return out.mode, out.size, list(i.getdata()) == list(out.getdata()) - # assert_equal(test("1"), ("1", (128, 100), True)) - assert_equal(test("L"), ("L", (128, 100), True)) - assert_equal(test("I"), ("I", (128, 100), True)) - assert_equal(test("F"), ("F", (128, 100), True)) - assert_equal(test("RGB"), ("RGB", (128, 100), True)) - assert_equal(test("RGBA"), ("RGBA", (128, 100), True)) - assert_equal(test("RGBX"), ("RGBA", (128, 100), True)) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py deleted file mode 100644 index 973606309..000000000 --- a/Tests/test_image_crop.py +++ /dev/null @@ -1,52 +0,0 @@ -from tester import * - -from PIL import Image - -def test_crop(): - def crop(mode): - out = lena(mode).crop((50, 50, 100, 100)) - assert_equal(out.mode, mode) - assert_equal(out.size, (50, 50)) - for mode in "1", "P", "L", "RGB", "I", "F": - yield_test(crop, mode) - -def test_wide_crop(): - - def crop(*bbox): - i = im.crop(bbox) - h = i.histogram() - while h and not h[-1]: - del h[-1] - return tuple(h) - - im = Image.new("L", (100, 100), 1) - - assert_equal(crop(0, 0, 100, 100), (0, 10000)) - assert_equal(crop(25, 25, 75, 75), (0, 2500)) - - # sides - assert_equal(crop(-25, 0, 25, 50), (1250, 1250)) - assert_equal(crop(0, -25, 50, 25), (1250, 1250)) - assert_equal(crop(75, 0, 125, 50), (1250, 1250)) - assert_equal(crop(0, 75, 50, 125), (1250, 1250)) - - assert_equal(crop(-25, 25, 125, 75), (2500, 5000)) - assert_equal(crop(25, -25, 75, 125), (2500, 5000)) - - # corners - assert_equal(crop(-25, -25, 25, 25), (1875, 625)) - assert_equal(crop(75, -25, 125, 25), (1875, 625)) - assert_equal(crop(75, 75, 125, 125), (1875, 625)) - assert_equal(crop(-25, 75, 25, 125), (1875, 625)) - -# -------------------------------------------------------------------- - -def test_negative_crop(): - # Check negative crop size (@PIL171) - - im = Image.new("L", (512, 512)) - im = im.crop((400, 400, 200, 200)) - - assert_equal(im.size, (0, 0)) - assert_equal(len(im.getdata()), 0) - assert_exception(IndexError, lambda: im.getdata()[0]) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py deleted file mode 100644 index f61e0c954..000000000 --- a/Tests/test_image_filter.py +++ /dev/null @@ -1,82 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageFilter - -def test_sanity(): - - def filter(filter): - im = lena("L") - out = im.filter(filter) - assert_equal(out.mode, im.mode) - assert_equal(out.size, im.size) - - filter(ImageFilter.BLUR) - filter(ImageFilter.CONTOUR) - filter(ImageFilter.DETAIL) - filter(ImageFilter.EDGE_ENHANCE) - filter(ImageFilter.EDGE_ENHANCE_MORE) - filter(ImageFilter.EMBOSS) - filter(ImageFilter.FIND_EDGES) - filter(ImageFilter.SMOOTH) - filter(ImageFilter.SMOOTH_MORE) - filter(ImageFilter.SHARPEN) - filter(ImageFilter.MaxFilter) - filter(ImageFilter.MedianFilter) - filter(ImageFilter.MinFilter) - filter(ImageFilter.ModeFilter) - filter(ImageFilter.Kernel((3, 3), list(range(9)))) - - assert_exception(TypeError, lambda: filter("hello")) - -def test_crash(): - - # crashes on small images - im = Image.new("RGB", (1, 1)) - assert_no_exception(lambda: im.filter(ImageFilter.SMOOTH)) - - im = Image.new("RGB", (2, 2)) - assert_no_exception(lambda: im.filter(ImageFilter.SMOOTH)) - - im = Image.new("RGB", (3, 3)) - assert_no_exception(lambda: im.filter(ImageFilter.SMOOTH)) - -def test_modefilter(): - - def modefilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 - mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - return mod, mod2 - - assert_equal(modefilter("1"), (4, 0)) - assert_equal(modefilter("L"), (4, 0)) - assert_equal(modefilter("P"), (4, 0)) - assert_equal(modefilter("RGB"), ((4, 0, 0), (0, 0, 0))) - -def test_rankfilter(): - - def rankfilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - min = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) - med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) - max = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) - return min, med, max - - assert_equal(rankfilter("1"), (0, 4, 8)) - assert_equal(rankfilter("L"), (0, 4, 8)) - assert_exception(ValueError, lambda: rankfilter("P")) - assert_equal(rankfilter("RGB"), ((0, 0, 0), (4, 0, 0), (8, 0, 0))) - assert_equal(rankfilter("I"), (0, 4, 8)) - assert_equal(rankfilter("F"), (0.0, 4.0, 8.0)) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py deleted file mode 100644 index e25359fdf..000000000 --- a/Tests/test_image_putdata.py +++ /dev/null @@ -1,40 +0,0 @@ -from tester import * - -import sys - -from PIL import Image - -def test_sanity(): - - im1 = lena() - - data = list(im1.getdata()) - - im2 = Image.new(im1.mode, im1.size, 0) - im2.putdata(data) - - assert_image_equal(im1, im2) - - # readonly - im2 = Image.new(im1.mode, im2.size, 0) - im2.readonly = 1 - im2.putdata(data) - - assert_false(im2.readonly) - assert_image_equal(im1, im2) - - -def test_long_integers(): - # see bug-200802-systemerror - def put(value): - im = Image.new("RGBA", (1, 1)) - im.putdata([value]) - return im.getpixel((0, 0)) - assert_equal(put(0xFFFFFFFF), (255, 255, 255, 255)) - assert_equal(put(0xFFFFFFFF), (255, 255, 255, 255)) - assert_equal(put(-1), (255, 255, 255, 255)) - assert_equal(put(-1), (255, 255, 255, 255)) - if sys.maxsize > 2**32: - assert_equal(put(sys.maxsize), (255, 255, 255, 255)) - else: - assert_equal(put(sys.maxsize), (255, 255, 255, 127)) diff --git a/Tests/test_imagefilter.py b/Tests/test_imagefilter.py deleted file mode 100644 index 214f88024..000000000 --- a/Tests/test_imagefilter.py +++ /dev/null @@ -1,31 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageFilter - -def test_sanity(): - # see test_image_filter for more tests - - assert_no_exception(lambda: ImageFilter.MaxFilter) - assert_no_exception(lambda: ImageFilter.MedianFilter) - assert_no_exception(lambda: ImageFilter.MinFilter) - assert_no_exception(lambda: ImageFilter.ModeFilter) - assert_no_exception(lambda: ImageFilter.Kernel((3, 3), list(range(9)))) - assert_no_exception(lambda: ImageFilter.GaussianBlur) - assert_no_exception(lambda: ImageFilter.GaussianBlur(5)) - assert_no_exception(lambda: ImageFilter.UnsharpMask) - assert_no_exception(lambda: ImageFilter.UnsharpMask(10)) - - assert_no_exception(lambda: ImageFilter.BLUR) - assert_no_exception(lambda: ImageFilter.CONTOUR) - assert_no_exception(lambda: ImageFilter.DETAIL) - assert_no_exception(lambda: ImageFilter.EDGE_ENHANCE) - assert_no_exception(lambda: ImageFilter.EDGE_ENHANCE_MORE) - assert_no_exception(lambda: ImageFilter.EMBOSS) - assert_no_exception(lambda: ImageFilter.FIND_EDGES) - assert_no_exception(lambda: ImageFilter.SMOOTH) - assert_no_exception(lambda: ImageFilter.SMOOTH_MORE) - assert_no_exception(lambda: ImageFilter.SHARPEN) - - - diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py deleted file mode 100644 index 02a461e22..000000000 --- a/Tests/test_imagestat.py +++ /dev/null @@ -1,52 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageStat - -def test_sanity(): - - im = lena() - - st = ImageStat.Stat(im) - st = ImageStat.Stat(im.histogram()) - st = ImageStat.Stat(im, Image.new("1", im.size, 1)) - - assert_no_exception(lambda: st.extrema) - assert_no_exception(lambda: st.sum) - assert_no_exception(lambda: st.mean) - assert_no_exception(lambda: st.median) - assert_no_exception(lambda: st.rms) - assert_no_exception(lambda: st.sum2) - assert_no_exception(lambda: st.var) - assert_no_exception(lambda: st.stddev) - assert_exception(AttributeError, lambda: st.spam) - - assert_exception(TypeError, lambda: ImageStat.Stat(1)) - -def test_lena(): - - im = lena() - - st = ImageStat.Stat(im) - - # verify a few values - assert_equal(st.extrema[0], (61, 255)) - assert_equal(st.median[0], 197) - assert_equal(st.sum[0], 2954416) - assert_equal(st.sum[1], 2027250) - assert_equal(st.sum[2], 1727331) - -def test_constant(): - - im = Image.new("L", (128, 128), 128) - - st = ImageStat.Stat(im) - - assert_equal(st.extrema[0], (128, 128)) - assert_equal(st.sum[0], 128**3) - assert_equal(st.sum2[0], 128**4) - assert_equal(st.mean[0], 128) - assert_equal(st.median[0], 128) - assert_equal(st.rms[0], 128) - assert_equal(st.var[0], 0) - assert_equal(st.stddev[0], 0) diff --git a/test/test_image_array.py b/test/test_image_array.py new file mode 100644 index 000000000..97599f9f0 --- /dev/null +++ b/test/test_image_array.py @@ -0,0 +1,46 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image + +im = lena().resize((128, 100)) + + +class TestImageCrop(PillowTestCase): + + def test_toarray(self): + def test(mode): + ai = im.convert(mode).__array_interface__ + return ai["shape"], ai["typestr"], len(ai["data"]) + # self.assertEqual(test("1"), ((100, 128), '|b1', 1600)) + self.assertEqual(test("L"), ((100, 128), '|u1', 12800)) + + # FIXME: wrong? + self.assertEqual(test("I"), ((100, 128), Image._ENDIAN + 'i4', 51200)) + # FIXME: wrong? + self.assertEqual(test("F"), ((100, 128), Image._ENDIAN + 'f4', 51200)) + + self.assertEqual(test("RGB"), ((100, 128, 3), '|u1', 38400)) + self.assertEqual(test("RGBA"), ((100, 128, 4), '|u1', 51200)) + self.assertEqual(test("RGBX"), ((100, 128, 4), '|u1', 51200)) + + def test_fromarray(self): + def test(mode): + i = im.convert(mode) + a = i.__array_interface__ + a["strides"] = 1 # pretend it's non-contigous + i.__array_interface__ = a # patch in new version of attribute + out = Image.fromarray(i) + return out.mode, out.size, list(i.getdata()) == list(out.getdata()) + # self.assertEqual(test("1"), ("1", (128, 100), True)) + self.assertEqual(test("L"), ("L", (128, 100), True)) + self.assertEqual(test("I"), ("I", (128, 100), True)) + self.assertEqual(test("F"), ("F", (128, 100), True)) + self.assertEqual(test("RGB"), ("RGB", (128, 100), True)) + self.assertEqual(test("RGBA"), ("RGBA", (128, 100), True)) + self.assertEqual(test("RGBX"), ("RGBA", (128, 100), True)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_crop.py b/test/test_image_crop.py new file mode 100644 index 000000000..a81ae000a --- /dev/null +++ b/test/test_image_crop.py @@ -0,0 +1,59 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImageCrop(PillowTestCase): + + def test_crop(self): + def crop(mode): + out = lena(mode).crop((50, 50, 100, 100)) + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, (50, 50)) + for mode in "1", "P", "L", "RGB", "I", "F": + crop(mode) + + def test_wide_crop(self): + + def crop(*bbox): + i = im.crop(bbox) + h = i.histogram() + while h and not h[-1]: + del h[-1] + return tuple(h) + + im = Image.new("L", (100, 100), 1) + + self.assertEqual(crop(0, 0, 100, 100), (0, 10000)) + self.assertEqual(crop(25, 25, 75, 75), (0, 2500)) + + # sides + self.assertEqual(crop(-25, 0, 25, 50), (1250, 1250)) + self.assertEqual(crop(0, -25, 50, 25), (1250, 1250)) + self.assertEqual(crop(75, 0, 125, 50), (1250, 1250)) + self.assertEqual(crop(0, 75, 50, 125), (1250, 1250)) + + self.assertEqual(crop(-25, 25, 125, 75), (2500, 5000)) + self.assertEqual(crop(25, -25, 75, 125), (2500, 5000)) + + # corners + self.assertEqual(crop(-25, -25, 25, 25), (1875, 625)) + self.assertEqual(crop(75, -25, 125, 25), (1875, 625)) + self.assertEqual(crop(75, 75, 125, 125), (1875, 625)) + self.assertEqual(crop(-25, 75, 25, 125), (1875, 625)) + + def test_negative_crop(self): + # Check negative crop size (@PIL171) + + im = Image.new("L", (512, 512)) + im = im.crop((400, 400, 200, 200)) + + self.assertEqual(im.size, (0, 0)) + self.assertEqual(len(im.getdata()), 0) + self.assertRaises(IndexError, lambda: im.getdata()[0]) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_filter.py b/test/test_image_filter.py new file mode 100644 index 000000000..02fa18c18 --- /dev/null +++ b/test/test_image_filter.py @@ -0,0 +1,91 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image +from PIL import ImageFilter + + +class TestImageFilter(PillowTestCase): + + def test_sanity(self): + + def filter(filter): + im = lena("L") + out = im.filter(filter) + self.assertEqual(out.mode, im.mode) + self.assertEqual(out.size, im.size) + + filter(ImageFilter.BLUR) + filter(ImageFilter.CONTOUR) + filter(ImageFilter.DETAIL) + filter(ImageFilter.EDGE_ENHANCE) + filter(ImageFilter.EDGE_ENHANCE_MORE) + filter(ImageFilter.EMBOSS) + filter(ImageFilter.FIND_EDGES) + filter(ImageFilter.SMOOTH) + filter(ImageFilter.SMOOTH_MORE) + filter(ImageFilter.SHARPEN) + filter(ImageFilter.MaxFilter) + filter(ImageFilter.MedianFilter) + filter(ImageFilter.MinFilter) + filter(ImageFilter.ModeFilter) + filter(ImageFilter.Kernel((3, 3), list(range(9)))) + + self.assertRaises(TypeError, lambda: filter("hello")) + + def test_crash(self): + + # crashes on small images + im = Image.new("RGB", (1, 1)) + im.filter(ImageFilter.SMOOTH) + + im = Image.new("RGB", (2, 2)) + im.filter(ImageFilter.SMOOTH) + + im = Image.new("RGB", (3, 3)) + im.filter(ImageFilter.SMOOTH) + + def test_modefilter(self): + + def modefilter(mode): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 + mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + return mod, mod2 + + self.assertEqual(modefilter("1"), (4, 0)) + self.assertEqual(modefilter("L"), (4, 0)) + self.assertEqual(modefilter("P"), (4, 0)) + self.assertEqual(modefilter("RGB"), ((4, 0, 0), (0, 0, 0))) + + def test_rankfilter(self): + + def rankfilter(mode): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + min = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) + med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) + max = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) + return min, med, max + + self.assertEqual(rankfilter("1"), (0, 4, 8)) + self.assertEqual(rankfilter("L"), (0, 4, 8)) + self.assertRaises(ValueError, lambda: rankfilter("P")) + self.assertEqual(rankfilter("RGB"), ((0, 0, 0), (4, 0, 0), (8, 0, 0))) + self.assertEqual(rankfilter("I"), (0, 4, 8)) + self.assertEqual(rankfilter("F"), (0.0, 4.0, 8.0)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_putdata.py b/test/test_image_putdata.py new file mode 100644 index 000000000..16e938bf8 --- /dev/null +++ b/test/test_image_putdata.py @@ -0,0 +1,48 @@ +from tester import unittest, PillowTestCase, lena + +import sys + +from PIL import Image + + +class TestImagePutData(PillowTestCase): + + def test_sanity(self): + + im1 = lena() + + data = list(im1.getdata()) + + im2 = Image.new(im1.mode, im1.size, 0) + im2.putdata(data) + + self.assert_image_equal(im1, im2) + + # readonly + im2 = Image.new(im1.mode, im2.size, 0) + im2.readonly = 1 + im2.putdata(data) + + self.assertFalse(im2.readonly) + self.assert_image_equal(im1, im2) + + def test_long_integers(self): + # see bug-200802-systemerror + def put(value): + im = Image.new("RGBA", (1, 1)) + im.putdata([value]) + return im.getpixel((0, 0)) + self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) + self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) + self.assertEqual(put(-1), (255, 255, 255, 255)) + self.assertEqual(put(-1), (255, 255, 255, 255)) + if sys.maxsize > 2**32: + self.assertEqual(put(sys.maxsize), (255, 255, 255, 255)) + else: + self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagefilter.py b/test/test_imagefilter.py new file mode 100644 index 000000000..1f01cb615 --- /dev/null +++ b/test/test_imagefilter.py @@ -0,0 +1,37 @@ +from tester import unittest, PillowTestCase + +from PIL import ImageFilter + + +class TestImageFilter(PillowTestCase): + + def test_sanity(self): + # see test_image_filter for more tests + + # Check these run. Exceptions cause failures. + ImageFilter.MaxFilter + ImageFilter.MedianFilter + ImageFilter.MinFilter + ImageFilter.ModeFilter + ImageFilter.Kernel((3, 3), list(range(9))) + ImageFilter.GaussianBlur + ImageFilter.GaussianBlur(5) + ImageFilter.UnsharpMask + ImageFilter.UnsharpMask(10) + + ImageFilter.BLUR + ImageFilter.CONTOUR + ImageFilter.DETAIL + ImageFilter.EDGE_ENHANCE + ImageFilter.EDGE_ENHANCE_MORE + ImageFilter.EMBOSS + ImageFilter.FIND_EDGES + ImageFilter.SMOOTH + ImageFilter.SMOOTH_MORE + ImageFilter.SHARPEN + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagestat.py b/test/test_imagestat.py new file mode 100644 index 000000000..fc228a886 --- /dev/null +++ b/test/test_imagestat.py @@ -0,0 +1,63 @@ +from tester import unittest, PillowTestCase, lena + +from PIL import Image +from PIL import ImageStat + + +class TestImageStat(PillowTestCase): + + def test_sanity(self): + + im = lena() + + st = ImageStat.Stat(im) + st = ImageStat.Stat(im.histogram()) + st = ImageStat.Stat(im, Image.new("1", im.size, 1)) + + # Check these run. Exceptions will cause failures. + st.extrema + st.sum + st.mean + st.median + st.rms + st.sum2 + st.var + st.stddev + + self.assertRaises(AttributeError, lambda: st.spam) + + self.assertRaises(TypeError, lambda: ImageStat.Stat(1)) + + def test_lena(self): + + im = lena() + + st = ImageStat.Stat(im) + + # verify a few values + self.assertEqual(st.extrema[0], (61, 255)) + self.assertEqual(st.median[0], 197) + self.assertEqual(st.sum[0], 2954416) + self.assertEqual(st.sum[1], 2027250) + self.assertEqual(st.sum[2], 1727331) + + def test_constant(self): + + im = Image.new("L", (128, 128), 128) + + st = ImageStat.Stat(im) + + self.assertEqual(st.extrema[0], (128, 128)) + self.assertEqual(st.sum[0], 128**3) + self.assertEqual(st.sum2[0], 128**4) + self.assertEqual(st.mean[0], 128) + self.assertEqual(st.median[0], 128) + self.assertEqual(st.rms[0], 128) + self.assertEqual(st.var[0], 0) + self.assertEqual(st.stddev[0], 0) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/tester.py b/test/tester.py index 8df32e317..418f1261f 100644 --- a/test/tester.py +++ b/test/tester.py @@ -161,17 +161,6 @@ def lena(mode="RGB", cache={}): # success() # # -# def assert_image_equal(a, b, msg=None): -# if a.mode != b.mode: -# failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) -# elif a.size != b.size: -# failure(msg or "got size %r, expected %r" % (a.size, b.size)) -# elif a.tobytes() != b.tobytes(): -# failure(msg or "got different content") -# else: -# success() -# -# # def assert_image_completely_equal(a, b, msg=None): # if a != b: # failure(msg or "images different") diff --git a/test/tests.py b/test/tests.py deleted file mode 100644 index 1bd308922..000000000 --- a/test/tests.py +++ /dev/null @@ -1,19 +0,0 @@ -from tester import unittest - - -class SomeTests(unittest.TestCase): - """ - Can we start moving the test suite here? - """ - - def test_suite_should_move_here(self): - """ - Great idea! - """ - self.assertTrue(True) - - -if __name__ == '__main__': - unittest.main() - -# End of file From f14b928ce68436ede7f4fcc083179dd89bddc1de Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 4 Jun 2014 10:11:22 +0300 Subject: [PATCH 070/488] Rename tester.py to helper.py because it no longer drives the tests --- test/{tester.py => helper.py} | 0 test/test_000_sanity.py | 2 +- test/test_image_array.py | 2 +- test/test_image_crop.py | 2 +- test/test_image_filter.py | 2 +- test/test_image_frombytes.py | 2 +- test/test_image_getim.py | 2 +- test/test_image_histogram.py | 2 +- test/test_image_putdata.py | 2 +- test/test_image_tobytes.py | 2 +- test/test_imagedraw.py | 2 +- test/test_imageenhance.py | 2 +- test/test_imagefilter.py | 2 +- test/test_imagestat.py | 2 +- test/test_lib_image.py | 2 +- 15 files changed, 14 insertions(+), 14 deletions(-) rename test/{tester.py => helper.py} (100%) diff --git a/test/tester.py b/test/helper.py similarity index 100% rename from test/tester.py rename to test/helper.py diff --git a/test/test_000_sanity.py b/test/test_000_sanity.py index b536dd96a..22e582ec3 100644 --- a/test/test_000_sanity.py +++ b/test/test_000_sanity.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase +from helper import unittest, PillowTestCase import PIL import PIL.Image diff --git a/test/test_image_array.py b/test/test_image_array.py index 97599f9f0..a0f5f29e1 100644 --- a/test/test_image_array.py +++ b/test/test_image_array.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_crop.py b/test/test_image_crop.py index a81ae000a..da93fe7c8 100644 --- a/test/test_image_crop.py +++ b/test/test_image_crop.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_filter.py b/test/test_image_filter.py index 02fa18c18..4a85b0a2e 100644 --- a/test/test_image_filter.py +++ b/test/test_image_filter.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageFilter diff --git a/test/test_image_frombytes.py b/test/test_image_frombytes.py index ee68b6722..aad8046a1 100644 --- a/test/test_image_frombytes.py +++ b/test/test_image_frombytes.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_getim.py b/test/test_image_getim.py index 02744a529..d498d3923 100644 --- a/test/test_image_getim.py +++ b/test/test_image_getim.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena, py3 +from helper import unittest, PillowTestCase, lena, py3 class TestImageGetIm(PillowTestCase): diff --git a/test/test_image_histogram.py b/test/test_image_histogram.py index 8a46149ff..70f78a1fb 100644 --- a/test/test_image_histogram.py +++ b/test/test_image_histogram.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageHistogram(PillowTestCase): diff --git a/test/test_image_putdata.py b/test/test_image_putdata.py index 16e938bf8..c7c3115aa 100644 --- a/test/test_image_putdata.py +++ b/test/test_image_putdata.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena import sys diff --git a/test/test_image_tobytes.py b/test/test_image_tobytes.py index ee7b4c9e6..3be9128c1 100644 --- a/test/test_image_tobytes.py +++ b/test/test_image_tobytes.py @@ -1,4 +1,4 @@ -from tester import unittest, lena +from helper import unittest, lena class TestImageToBytes(unittest.TestCase): diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py index f9a5e5f6a..44403e50c 100644 --- a/test/test_imagedraw.py +++ b/test/test_imagedraw.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageColor diff --git a/test/test_imageenhance.py b/test/test_imageenhance.py index c126bf344..433c49cf6 100644 --- a/test/test_imageenhance.py +++ b/test/test_imageenhance.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageEnhance diff --git a/test/test_imagefilter.py b/test/test_imagefilter.py index 1f01cb615..f7edb409a 100644 --- a/test/test_imagefilter.py +++ b/test/test_imagefilter.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import ImageFilter diff --git a/test/test_imagestat.py b/test/test_imagestat.py index fc228a886..4d30ff023 100644 --- a/test/test_imagestat.py +++ b/test/test_imagestat.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageStat diff --git a/test/test_lib_image.py b/test/test_lib_image.py index 8694c1d51..e0a903b00 100644 --- a/test/test_lib_image.py +++ b/test/test_lib_image.py @@ -1,4 +1,4 @@ -from tester import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image From 184c380e106a48aac92f3fd58b70c8b3a82b6d28 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 4 Jun 2014 16:18:04 +0300 Subject: [PATCH 071/488] Convert more tests, including a method to skip on ImportError --- Tests/test_image_copy.py | 12 ------------ Tests/test_imagegrab.py | 13 ------------- test/helper.py | 20 +++++++++++--------- test/test_image_copy.py | 20 ++++++++++++++++++++ test/test_imagegrab.py | 25 +++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 34 deletions(-) delete mode 100644 Tests/test_image_copy.py delete mode 100644 Tests/test_imagegrab.py create mode 100644 test/test_image_copy.py create mode 100644 test/test_imagegrab.py diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py deleted file mode 100644 index 40a3dc496..000000000 --- a/Tests/test_image_copy.py +++ /dev/null @@ -1,12 +0,0 @@ -from tester import * - -from PIL import Image - -def test_copy(): - def copy(mode): - im = lena(mode) - out = im.copy() - assert_equal(out.mode, mode) - assert_equal(out.size, im.size) - for mode in "1", "P", "L", "RGB", "I", "F": - yield_test(copy, mode) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py deleted file mode 100644 index 67ff71960..000000000 --- a/Tests/test_imagegrab.py +++ /dev/null @@ -1,13 +0,0 @@ -from tester import * - -from PIL import Image -try: - from PIL import ImageGrab -except ImportError as v: - skip(v) - -def test_grab(): - im = ImageGrab.grab() - assert_image(im, im.mode, im.size) - - diff --git a/test/helper.py b/test/helper.py index 418f1261f..8c45858b6 100644 --- a/test/helper.py +++ b/test/helper.py @@ -12,6 +12,17 @@ else: class PillowTestCase(unittest.TestCase): + def assert_image(self, im, mode, size, msg=None): + if mode is not None: + self.assertEqual( + im.mode, mode, + msg or "got mode %r, expected %r" % (im.mode, mode)) + + if size is not None: + self.assertEqual( + im.size, size, + msg or "got size %r, expected %r" % (im.size, size)) + def assert_image_equal(self, a, b, msg=None): self.assertEqual( a.mode, b.mode, @@ -152,15 +163,6 @@ def lena(mode="RGB", cache={}): return im -# def assert_image(im, mode, size, msg=None): -# if mode is not None and im.mode != mode: -# failure(msg or "got mode %r, expected %r" % (im.mode, mode)) -# elif size is not None and im.size != size: -# failure(msg or "got size %r, expected %r" % (im.size, size)) -# else: -# success() -# -# # def assert_image_completely_equal(a, b, msg=None): # if a != b: # failure(msg or "images different") diff --git a/test/test_image_copy.py b/test/test_image_copy.py new file mode 100644 index 000000000..a7882db94 --- /dev/null +++ b/test/test_image_copy.py @@ -0,0 +1,20 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImageCopy(PillowTestCase): + + def test_copy(self): + def copy(mode): + im = lena(mode) + out = im.copy() + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, im.size) + for mode in "1", "P", "L", "RGB", "I", "F": + copy(mode) + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagegrab.py b/test/test_imagegrab.py new file mode 100644 index 000000000..fb953f435 --- /dev/null +++ b/test/test_imagegrab.py @@ -0,0 +1,25 @@ +from helper import unittest, PillowTestCase + +try: + from PIL import ImageGrab + + class TestImageCopy(PillowTestCase): + + def test_grab(self): + im = ImageGrab.grab() + self.assert_image(im, im.mode, im.size) + + def test_grab2(self): + im = ImageGrab.grab() + self.assert_image(im, im.mode, im.size) + +except ImportError as v: + class TestImageCopy(PillowTestCase): + def test_skip(self): + self.skipTest(v) + + +if __name__ == '__main__': + unittest.main() + +# End of file From b7eb1de922791f1604aac91c032de5cf4e3e2490 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 4 Jun 2014 17:09:59 +0300 Subject: [PATCH 072/488] Fix skipping --- test/test_imagegrab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_imagegrab.py b/test/test_imagegrab.py index fb953f435..2275d34a1 100644 --- a/test/test_imagegrab.py +++ b/test/test_imagegrab.py @@ -13,10 +13,10 @@ try: im = ImageGrab.grab() self.assert_image(im, im.mode, im.size) -except ImportError as v: +except ImportError: class TestImageCopy(PillowTestCase): def test_skip(self): - self.skipTest(v) + self.skipTest("ImportError") if __name__ == '__main__': From 5fe80c6d4aeda9605bd5c0ca765e1dde92a9e24e Mon Sep 17 00:00:00 2001 From: Dov Grobgeld Date: Wed, 4 Jun 2014 23:03:00 +0300 Subject: [PATCH 073/488] Initial commit of binary morphology addon. --- PIL/ImageMorph.py | 234 ++++++++++++++++++++++++++++++++ Tests/test_imagemorph.py | 138 +++++++++++++++++++ _imagingmorph.c | 286 +++++++++++++++++++++++++++++++++++++++ setup.py | 3 + 4 files changed, 661 insertions(+) create mode 100644 PIL/ImageMorph.py create mode 100644 Tests/test_imagemorph.py create mode 100644 _imagingmorph.c diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py new file mode 100644 index 000000000..a78e341ed --- /dev/null +++ b/PIL/ImageMorph.py @@ -0,0 +1,234 @@ +# A binary morphology add-on for the Python Imaging Library +# +# History: +# 2014-06-04 Initial version. +# +# Copyright (c) 2014 Dov Grobgeld + +from PIL import Image +from PIL import _imagingmorph +import re + +LUT_SIZE = 1<<9 +class LutBuilder: + """A class for building MorphLut's from a descriptive language + + The input patterns is a list of a strings sequences like these: + + 4:(... + .1. + 111)->1 + + (whitespaces including linebreaks are ignored). The option 4 + descibes a series of symmetry operations (in this case a + 4-rotation), the pattern is decribed by: + + . or X - Ignore + 1 - Pixel is on + 0 - Pixel is off + + The result of the operation is described after "->" string. + + The default is to return the current pixel value, which is + returned if no other match is found. + + Operations: + 4 - 4 way rotation + N - Negate + 1 - Dummy op for no other operation (an op must always be given) + M - Mirroring + + Example: + + lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) + lut = lb.build_lut() + + """ + def __init__(self,patterns = None,op_name=None): + if patterns is not None: + self.patterns = patterns + else: + self.patterns = [] + self.lut = None + if op_name is not None: + known_patterns = { + 'corner' : ['1:(... ... ...)->0', + '4:(00. 01. ...)->1'], + 'dilation4' : ['4:(... .0. .1.)->1'], + 'dilation8' : ['4:(... .0. .1.)->1', + '4:(... .0. ..1)->1'], + 'erosion4' : ['4:(... .1. .0.)->0'], + 'erosion8' : ['4:(... .1. .0.)->0', + '4:(... .1. ..0)->0'], + 'edge' : ['1:(... ... ...)->0', + '4:(.0. .1. ...)->1', + '4:(01. .1. ...)->1'] + } + if not op_name in known_patterns: + raise Exception('Unknown pattern '+op_name+'!') + + self.patterns = known_patterns[op_name] + + def add_patterns(self, patterns): + self.patterns += patterns + + def build_default_lut(self): + symbols = ['\0','\1'] + m = 1 << 4 # pos of current pixel + self.lut = bytearray(''.join([symbols[(i & m)>0] for i in range(LUT_SIZE)])) + + def get_lut(self): + return self.lut + + def _string_permute(self, pattern, permutation): + """string_permute takes a pattern and a permutation and returns the + string permuted accordinging to the permutation list. + """ + assert(len(permutation)==9) + return ''.join([pattern[p] for p in permutation]) + + def _pattern_permute(self, basic_pattern, options, basic_result): + """pattern_permute takes a basic pattern and its result and clones + the mattern according to the modifications described in the $options + parameter. It returns a list of all cloned patterns.""" + patterns = [(basic_pattern, basic_result)] + + # rotations + if '4' in options: + res = patterns[-1][1] + for i in range(4): + patterns.append( + (self._string_permute(patterns[-1][0], + [6,3,0, + 7,4,1, + 8,5,2]), res)) + # mirror + if 'M' in options: + n = len(patterns) + for pattern,res in patterns[0:n]: + patterns.append( + (self._string_permute(pattern, [2,1,0, + 5,4,3, + 8,7,6]), res)) + + # negate + if 'N' in options: + n = len(patterns) + for pattern,res in patterns[0:n]: + # Swap 0 and 1 + pattern = (pattern + .replace('0','Z') + .replace('1','0') + .replace('Z','1')) + res = '%d'%(1-int(res)) + patterns.append((pattern, res)) + + return patterns + + def build_lut(self): + """Compile all patterns into a morphology lut. + + TBD :Build based on (file) morphlut:modify_lut + """ + self.build_default_lut() + patterns = [] + + # Parse and create symmetries of the patterns strings + for p in self.patterns: + m = re.search(r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n','')) + if not m: + raise Exception('Syntax error in pattern "'+p+'"') + options = m.group(1) + pattern = m.group(2) + result = int(m.group(3)) + + # Get rid of spaces + pattern= pattern.replace(' ','').replace('\n','') + + patterns += self._pattern_permute(pattern, options, result) + +# # Debugging +# for p,r in patterns: +# print p,r +# print '--' + + # compile the patterns into regular expressions for speed + for i in range(len(patterns)): + p = patterns[i][0].replace('.','X').replace('X','[01]') + p = re.compile(p) + patterns[i] = (p, patterns[i][1]) + + # Step through table and find patterns that match. + # Note that all the patterns are searched. The last one + # caught overrides + for i in range(LUT_SIZE): + # Build the bit pattern + bitpattern = bin(i)[2:] + bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1] + + for p,r in patterns: + if p.match(bitpattern): + self.lut[i] = ['\0','\1'][r] + + return self.lut + +class MorphOp: + """A class for binary morphological operators""" + + def __init__(self, + lut=None, + op_name = None, + patterns = None): + """Create a binary morphological operator""" + self.lut = lut + if op_name is not None: + self.lut = LutBuilder(op_name = op_name).build_lut() + elif patterns is not None: + self.lut = LutBuilder(patterns = patterns).build_lut() + + def apply(self, image): + """Run a single morphological operation on an image + + Returns a tuple of the number of changed pixels and the + morphed image""" + if self.lut is None: + raise Exception('No operator loaded') + + outimage = Image.new(image.mode, image.size, None) + count = _imagingmorph.apply(str(self.lut), image.im.id, outimage.im.id) + return count, outimage + + def match(self, image): + """Get a list of coordinates matching the morphological operation on an image + + Returns a list of tuples of (x,y) coordinates of all matching pixels.""" + if self.lut is None: + raise Exception('No operator loaded') + + return _imagingmorph.match(str(self.lut), image.im.id) + + def get_on_pixels(self, image): + """Get a list of all turned on pixels in a binary image + + Returns a list of tuples of (x,y) coordinates of all matching pixels.""" + + return _imagingmorph.get_on_pixels(image.im.id) + + def load_lut(self, filename): + """Load an operator from an mrl file""" + self.lut = bytearray(open(filename,'rb').read()) + if len(self.lut)!= 8192: + self.lut = None + raise Exception('Wrong size operator file!') + + def save_lut(self, filename): + """Load an operator save mrl file""" + if self.lut is None: + raise Exception('No operator loaded') + open(filename,'wb').write(self.lut) + + def set_lut(self, lut): + """Set the lut from an external source""" + self.lut = lut + + diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py new file mode 100644 index 000000000..7d0ce8796 --- /dev/null +++ b/Tests/test_imagemorph.py @@ -0,0 +1,138 @@ +# Test the ImageMorphology functionality +from tester import * + +from PIL import Image +from PIL import ImageMorph + +def img_to_string(im): + """Turn a (small) binary image into a string representation""" + chars = '.1' + width, height = im.size + return '\n'.join( + [''.join([chars[im.getpixel((c,r))>0] for c in range(width)]) + for r in range(height)]) + +def string_to_img(image_string): + """Turn a string image representation into a binary image""" + rows = [s for s in image_string.replace(' ','').split('\n') + if len(s)] + height = len(rows) + width = len(rows[0]) + im = Image.new('L',(width,height)) + for i in range(width): + for j in range(height): + c = rows[j][i] + v = c in 'X1' + im.putpixel((i,j),v) + + return im + +def img_string_normalize(im): + return img_to_string(string_to_img(im)) + +def assert_img_equal(A,B): + assert_equal(img_to_string(A), img_to_string(B)) + +def assert_img_equal_img_string(A,Bstring): + assert_equal(img_to_string(A), img_string_normalize(Bstring)) + +A = string_to_img( +""" +....... +....... +..111.. +..111.. +..111.. +....... +....... +""" +) + +# Test the named patterns + +# erosion8 +mop = ImageMorph.MorphOp(op_name='erosion8') +count,Aout = mop.apply(A) +assert_equal(count,8) +assert_img_equal_img_string(Aout, +""" +....... +....... +....... +...1... +....... +....... +....... +""") + +# erosion8 +mop = ImageMorph.MorphOp(op_name='dilation8') +count,Aout = mop.apply(A) +assert_equal(count,16) +assert_img_equal_img_string(Aout, +""" +....... +.11111. +.11111. +.11111. +.11111. +.11111. +....... +""") + +# erosion4 +mop = ImageMorph.MorphOp(op_name='dilation4') +count,Aout = mop.apply(A) +assert_equal(count,12) +assert_img_equal_img_string(Aout, +""" +....... +..111.. +.11111. +.11111. +.11111. +..111.. +....... +""") + +# edge +mop = ImageMorph.MorphOp(op_name='edge') +count,Aout = mop.apply(A) +assert_equal(count,1) +assert_img_equal_img_string(Aout, +""" +....... +....... +..111.. +..1.1.. +..111.. +....... +....... +""") + +# Create a corner detector pattern +mop = ImageMorph.MorphOp(patterns = ['1:(... ... ...)->0', + '4:(00. 01. ...)->1']) +count,Aout = mop.apply(A) +assert_equal(count,5) +assert_img_equal_img_string(Aout, +""" +....... +....... +..1.1.. +....... +..1.1.. +....... +....... +""") + +# Test the coordinate counting with the same operator +coords = mop.match(A) +assert_equal(len(coords), 4) +assert_equal(tuple(coords), + ((2,2),(4,2),(2,4),(4,4))) + +coords = mop.get_on_pixels(Aout) +assert_equal(len(coords), 4) +assert_equal(tuple(coords), + ((2,2),(4,2),(2,4),(4,4))) diff --git a/_imagingmorph.c b/_imagingmorph.c new file mode 100644 index 000000000..f80124022 --- /dev/null +++ b/_imagingmorph.c @@ -0,0 +1,286 @@ +/* + * The Python Imaging Library + * + * A binary morphology add-on for the Python Imaging Library + * + * History: + * 2014-06-04 Initial version. + * + * Copyright (c) 2014 Dov Grobgeld + * + * See the README file for information on usage and redistribution. + */ + +#include "Python.h" +#include "Imaging.h" +#include "py3.h" + +#define LUT_SIZE (1<<9) + +/* Apply a morphologic LUT to a binary image. Outputs a + a new binary image. + + Expected parameters: + + 1. a LUT - a 512 byte size lookup table. + 2. an input Imaging image id. + 3. an output Imaging image id + + Returns number of changed pixels. +*/ +static PyObject* +apply(PyObject *self, PyObject* args) +{ + const char *lut; + Py_ssize_t lut_len, i0, i1; + Imaging imgin, imgout; + int width, height; + int row_idx, col_idx; + UINT8 **inrows, **outrows; + int num_changed_pixels = 0; + + if (!PyArg_ParseTuple(args, "s#nn", &lut, &lut_len, &i0, &i1)) { + PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); + + return NULL; + } + + if (lut_len < LUT_SIZE) { + PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); + return NULL; + } + + imgin = (Imaging) i0; + imgout = (Imaging) i1; + width = imgin->xsize; + height = imgin->ysize; + + if (imgin->type != IMAGING_TYPE_UINT8 && + imgin->bands != 1) { + PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); + return NULL; + } + if (imgout->type != IMAGING_TYPE_UINT8 && + imgout->bands != 1) { + PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); + return NULL; + } + + inrows = imgin->image8; + outrows = imgout->image8; + + for (row_idx=0; row_idx < height; row_idx++) { + UINT8 *outrow = outrows[row_idx]; + UINT8 *inrow = inrows[row_idx]; + UINT8 *prow, *nrow; /* Previous and next row */ + + /* zero boundary conditions. TBD support other modes */ + outrow[0] = outrow[width-1] = 0; + if (row_idx==0 || row_idx == height-1) { + for(col_idx=0; col_idxtype != IMAGING_TYPE_UINT8 && + imgin->bands != 1) { + PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); + return NULL; + } + + inrows = imgin->image8; + width = imgin->xsize; + height = imgin->ysize; + + for (row_idx=1; row_idx < height-1; row_idx++) { + UINT8 *inrow = inrows[row_idx]; + UINT8 *prow, *nrow; + + prow = inrows[row_idx-1]; + nrow = inrows[row_idx+1]; + + for (col_idx=1; col_idximage8; + width = img->xsize; + height = img->ysize; + + for (row_idx=0; row_idx < height; row_idx++) { + UINT8 *row = rows[row_idx]; + for (col_idx=0; col_idx= 0x03000000 +PyMODINIT_FUNC +PyInit__imagingmorph(void) { + PyObject* m; + + static PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "_imagingmorph", /* m_name */ + "A module for doing image morphology", /* m_doc */ + -1, /* m_size */ + functions, /* m_methods */ + }; + + m = PyModule_Create(&module_def); + + if (setup_module(m) < 0) + return NULL; + + return m; +} +#else +PyMODINIT_FUNC +init_imagingmorph(void) +{ + PyObject* m = Py_InitModule("_imagingmorph", functions); + setup_module(m); +} +#endif + diff --git a/setup.py b/setup.py index 50ca985e3..cee913637 100644 --- a/setup.py +++ b/setup.py @@ -543,6 +543,9 @@ class pil_build_ext(build_ext): if os.path.isfile("_imagingmath.c"): exts.append(Extension("PIL._imagingmath", ["_imagingmath.c"])) + if os.path.isfile("_imagingmorph.c"): + exts.append(Extension("PIL._imagingmorph", ["_imagingmorph.c"])) + self.extensions[:] = exts build_ext.build_extensions(self) From 6858aef5b15823855f24b3f47e3d3a307ca9841a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 4 Jun 2014 18:32:17 -0400 Subject: [PATCH 074/488] Update --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fbe2d3b0c..71a42e315 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,10 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ + +- ImageCms fixes + [hugovk] + - Added more ImageDraw tests [hugovk] From d385dd81a7c3a425e0052f81bbbaa5e2ebb5a575 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 11:39:29 +0300 Subject: [PATCH 075/488] Converttest_image_load.py --- Tests/test_image_load.py | 27 --------------------------- test/test_image_load.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 27 deletions(-) delete mode 100644 Tests/test_image_load.py create mode 100644 test/test_image_load.py diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py deleted file mode 100644 index b385b9686..000000000 --- a/Tests/test_image_load.py +++ /dev/null @@ -1,27 +0,0 @@ -from tester import * - -from PIL import Image - -import os - -def test_sanity(): - - im = lena() - - pix = im.load() - - assert_equal(pix[0, 0], (223, 162, 133)) - -def test_close(): - im = Image.open("Images/lena.gif") - assert_no_exception(lambda: im.close()) - assert_exception(ValueError, lambda: im.load()) - assert_exception(ValueError, lambda: im.getpixel((0,0))) - -def test_contextmanager(): - fn = None - with Image.open("Images/lena.gif") as im: - fn = im.fp.fileno() - assert_no_exception(lambda: os.fstat(fn)) - - assert_exception(OSError, lambda: os.fstat(fn)) diff --git a/test/test_image_load.py b/test/test_image_load.py new file mode 100644 index 000000000..80f505e6c --- /dev/null +++ b/test/test_image_load.py @@ -0,0 +1,35 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + +import os + + +class TestImageLoad(PillowTestCase): + + def test_sanity(self): + + im = lena() + + pix = im.load() + + self.assertEqual(pix[0, 0], (223, 162, 133)) + + def test_close(self): + im = Image.open("Images/lena.gif") + im.close() + self.assert_exception(ValueError, lambda: im.load()) + self.assert_exception(ValueError, lambda: im.getpixel((0, 0))) + + def test_contextmanager(self): + fn = None + with Image.open("Images/lena.gif") as im: + fn = im.fp.fileno() + os.fstat(fn) + + self.assert_exception(OSError, lambda: os.fstat(fn)) + +if __name__ == '__main__': + unittest.main() + +# End of file From 133120c6a655e933186f6da64a2b5548f6431d97 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 11:45:01 +0300 Subject: [PATCH 076/488] assert_exception is assertRaises --- .travis.yml | 4 ++-- test/test_image_load.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a2250771..7e4e5e9c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,13 +30,13 @@ script: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then nosetests test/; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi # Cover the others - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* -m nose test/; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi after_success: diff --git a/test/test_image_load.py b/test/test_image_load.py index 80f505e6c..2001c233a 100644 --- a/test/test_image_load.py +++ b/test/test_image_load.py @@ -18,8 +18,8 @@ class TestImageLoad(PillowTestCase): def test_close(self): im = Image.open("Images/lena.gif") im.close() - self.assert_exception(ValueError, lambda: im.load()) - self.assert_exception(ValueError, lambda: im.getpixel((0, 0))) + self.assertRaises(ValueError, lambda: im.load()) + self.assertRaises(ValueError, lambda: im.getpixel((0, 0))) def test_contextmanager(self): fn = None @@ -27,7 +27,7 @@ class TestImageLoad(PillowTestCase): fn = im.fp.fileno() os.fstat(fn) - self.assert_exception(OSError, lambda: os.fstat(fn)) + self.assertRaises(OSError, lambda: os.fstat(fn)) if __name__ == '__main__': unittest.main() From 82df06243c1aa5d2c5d7f75262e83dfad7620f54 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 12:26:54 +0300 Subject: [PATCH 077/488] Convert more tests --- test/test_image_tobitmap.py | 24 ++++++++++++ test/test_imagechops.py | 74 +++++++++++++++++++++++++++++++++++ test/test_imagemath.py | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 test/test_image_tobitmap.py create mode 100644 test/test_imagechops.py create mode 100644 test/test_imagemath.py diff --git a/test/test_image_tobitmap.py b/test/test_image_tobitmap.py new file mode 100644 index 000000000..00e5af0cd --- /dev/null +++ b/test/test_image_tobitmap.py @@ -0,0 +1,24 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImage(PillowTestCase): + +def test_sanity(self): + + self.assertRaises(ValueError, lambda: lena().tobitmap()) + assert_no_exception(lambda: lena().convert("1").tobitmap()) + + im1 = lena().convert("1") + + bitmap = im1.tobitmap() + + assert_true(isinstance(bitmap, bytes)) + assert_image_equal(im1, fromstring(bitmap)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagechops.py b/test/test_imagechops.py new file mode 100644 index 000000000..ec162d52f --- /dev/null +++ b/test/test_imagechops.py @@ -0,0 +1,74 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image +from PIL import ImageChops + + +class TestImage(PillowTestCase): + + def test_sanity(self): + + im = lena("L") + + ImageChops.constant(im, 128) + ImageChops.duplicate(im) + ImageChops.invert(im) + ImageChops.lighter(im, im) + ImageChops.darker(im, im) + ImageChops.difference(im, im) + ImageChops.multiply(im, im) + ImageChops.screen(im, im) + + ImageChops.add(im, im) + ImageChops.add(im, im, 2.0) + ImageChops.add(im, im, 2.0, 128) + ImageChops.subtract(im, im) + ImageChops.subtract(im, im, 2.0) + ImageChops.subtract(im, im, 2.0, 128) + + ImageChops.add_modulo(im, im) + ImageChops.subtract_modulo(im, im) + + ImageChops.blend(im, im, 0.5) + ImageChops.composite(im, im, im) + + ImageChops.offset(im, 10) + ImageChops.offset(im, 10, 20) + + def test_logical(self): + + def table(op, a, b): + out = [] + for x in (a, b): + imx = Image.new("1", (1, 1), x) + for y in (a, b): + imy = Image.new("1", (1, 1), y) + out.append(op(imx, imy).getpixel((0, 0))) + return tuple(out) + + self.assertEqual( + table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) + self.assertEqual( + table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) + self.assertEqual( + table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) + + self.assertEqual( + table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) + self.assertEqual( + table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) + self.assertEqual( + table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) + + self.assertEqual( + table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) + self.assertEqual( + table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) + self.assertEqual( + table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagemath.py b/test/test_imagemath.py new file mode 100644 index 000000000..17d43d25a --- /dev/null +++ b/test/test_imagemath.py @@ -0,0 +1,78 @@ +from helper import unittest, PillowTestCase + +from PIL import Image +from PIL import ImageMath + + +def pixel(im): + if hasattr(im, "im"): + return "%s %r" % (im.mode, im.getpixel((0, 0))) + else: + if isinstance(im, type(0)): + return int(im) # hack to deal with booleans + print(im) + +A = Image.new("L", (1, 1), 1) +B = Image.new("L", (1, 1), 2) +F = Image.new("F", (1, 1), 3) +I = Image.new("I", (1, 1), 4) + +images = {"A": A, "B": B, "F": F, "I": I} + + +class TestImageMath(PillowTestCase): + + def test_sanity(self): + self.assertEqual(ImageMath.eval("1"), 1) + self.assertEqual(ImageMath.eval("1+A", A=2), 3) + self.assertEqual(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") + self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") + self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") + self.assertEqual(pixel( + ImageMath.eval("int(float(A)+B)", images)), "I 3") + + def test_ops(self): + + self.assertEqual(pixel(ImageMath.eval("-A", images)), "I -1") + self.assertEqual(pixel(ImageMath.eval("+B", images)), "L 2") + + self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") + self.assertEqual(pixel(ImageMath.eval("A-B", images)), "I -1") + self.assertEqual(pixel(ImageMath.eval("A*B", images)), "I 2") + self.assertEqual(pixel(ImageMath.eval("A/B", images)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B**2", images)), "I 4") + self.assertEqual(pixel( + ImageMath.eval("B**33", images)), "I 2147483647") + + self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") + self.assertEqual(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") + self.assertEqual(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") + self.assertEqual(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") + self.assertEqual(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") + self.assertEqual(pixel( + ImageMath.eval("float(B)**33", images)), "F 8589934592.0") + + def test_logical(self): + self.assertEqual(pixel(ImageMath.eval("not A", images)), 0) + self.assertEqual(pixel(ImageMath.eval("A and B", images)), "L 2") + self.assertEqual(pixel(ImageMath.eval("A or B", images)), "L 1") + + def test_convert(self): + self.assertEqual(pixel( + ImageMath.eval("convert(A+B, 'L')", images)), "L 3") + self.assertEqual(pixel( + ImageMath.eval("convert(A+B, '1')", images)), "1 0") + self.assertEqual(pixel( + ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") + + def test_compare(self): + self.assertEqual(pixel(ImageMath.eval("min(A, B)", images)), "I 1") + self.assertEqual(pixel(ImageMath.eval("max(A, B)", images)), "I 2") + self.assertEqual(pixel(ImageMath.eval("A == 1", images)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A == 2", images)), "I 0") + + +if __name__ == '__main__': + unittest.main() + +# End of file From 1f94ea1fc613bc0fb1489235f84b673d8ca06d4a Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 12:30:59 +0300 Subject: [PATCH 078/488] Merge --- Tests/test_image_tobitmap.py | 15 --------- Tests/test_imagechops.py | 56 -------------------------------- Tests/test_imagemath.py | 62 ------------------------------------ 3 files changed, 133 deletions(-) delete mode 100644 Tests/test_image_tobitmap.py delete mode 100644 Tests/test_imagechops.py delete mode 100644 Tests/test_imagemath.py diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py deleted file mode 100644 index 6fb10dd53..000000000 --- a/Tests/test_image_tobitmap.py +++ /dev/null @@ -1,15 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - - assert_exception(ValueError, lambda: lena().tobitmap()) - assert_no_exception(lambda: lena().convert("1").tobitmap()) - - im1 = lena().convert("1") - - bitmap = im1.tobitmap() - - assert_true(isinstance(bitmap, bytes)) - assert_image_equal(im1, fromstring(bitmap)) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py deleted file mode 100644 index 16eaaf55e..000000000 --- a/Tests/test_imagechops.py +++ /dev/null @@ -1,56 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageChops - -def test_sanity(): - - im = lena("L") - - ImageChops.constant(im, 128) - ImageChops.duplicate(im) - ImageChops.invert(im) - ImageChops.lighter(im, im) - ImageChops.darker(im, im) - ImageChops.difference(im, im) - ImageChops.multiply(im, im) - ImageChops.screen(im, im) - - ImageChops.add(im, im) - ImageChops.add(im, im, 2.0) - ImageChops.add(im, im, 2.0, 128) - ImageChops.subtract(im, im) - ImageChops.subtract(im, im, 2.0) - ImageChops.subtract(im, im, 2.0, 128) - - ImageChops.add_modulo(im, im) - ImageChops.subtract_modulo(im, im) - - ImageChops.blend(im, im, 0.5) - ImageChops.composite(im, im, im) - - ImageChops.offset(im, 10) - ImageChops.offset(im, 10, 20) - -def test_logical(): - - def table(op, a, b): - out = [] - for x in (a, b): - imx = Image.new("1", (1, 1), x) - for y in (a, b): - imy = Image.new("1", (1, 1), y) - out.append(op(imx, imy).getpixel((0, 0))) - return tuple(out) - - assert_equal(table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) - assert_equal(table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) - assert_equal(table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) - - assert_equal(table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) - assert_equal(table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) - assert_equal(table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) - - assert_equal(table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) - assert_equal(table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) - assert_equal(table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py deleted file mode 100644 index eaeb711ba..000000000 --- a/Tests/test_imagemath.py +++ /dev/null @@ -1,62 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageMath - -def pixel(im): - if hasattr(im, "im"): - return "%s %r" % (im.mode, im.getpixel((0, 0))) - else: - if isinstance(im, type(0)): - return int(im) # hack to deal with booleans - print(im) - -A = Image.new("L", (1, 1), 1) -B = Image.new("L", (1, 1), 2) -F = Image.new("F", (1, 1), 3) -I = Image.new("I", (1, 1), 4) - -images = {"A": A, "B": B, "F": F, "I": I} - -def test_sanity(): - assert_equal(ImageMath.eval("1"), 1) - assert_equal(ImageMath.eval("1+A", A=2), 3) - assert_equal(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") - assert_equal(pixel(ImageMath.eval("A+B", images)), "I 3") - assert_equal(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - assert_equal(pixel(ImageMath.eval("int(float(A)+B)", images)), "I 3") - -def test_ops(): - - assert_equal(pixel(ImageMath.eval("-A", images)), "I -1") - assert_equal(pixel(ImageMath.eval("+B", images)), "L 2") - - assert_equal(pixel(ImageMath.eval("A+B", images)), "I 3") - assert_equal(pixel(ImageMath.eval("A-B", images)), "I -1") - assert_equal(pixel(ImageMath.eval("A*B", images)), "I 2") - assert_equal(pixel(ImageMath.eval("A/B", images)), "I 0") - assert_equal(pixel(ImageMath.eval("B**2", images)), "I 4") - assert_equal(pixel(ImageMath.eval("B**33", images)), "I 2147483647") - - assert_equal(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - assert_equal(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") - assert_equal(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") - assert_equal(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") - assert_equal(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") - assert_equal(pixel(ImageMath.eval("float(B)**33", images)), "F 8589934592.0") - -def test_logical(): - assert_equal(pixel(ImageMath.eval("not A", images)), 0) - assert_equal(pixel(ImageMath.eval("A and B", images)), "L 2") - assert_equal(pixel(ImageMath.eval("A or B", images)), "L 1") - -def test_convert(): - assert_equal(pixel(ImageMath.eval("convert(A+B, 'L')", images)), "L 3") - assert_equal(pixel(ImageMath.eval("convert(A+B, '1')", images)), "1 0") - assert_equal(pixel(ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") - -def test_compare(): - assert_equal(pixel(ImageMath.eval("min(A, B)", images)), "I 1") - assert_equal(pixel(ImageMath.eval("max(A, B)", images)), "I 2") - assert_equal(pixel(ImageMath.eval("A == 1", images)), "I 1") - assert_equal(pixel(ImageMath.eval("A == 2", images)), "I 0") From 34db40ddce99e6c4daf9b64873689d1249cb7a0f Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 12:39:15 +0300 Subject: [PATCH 079/488] Convert test_image_tobitmap.py --- test/helper.py | 31 +++++++++++++++---------------- test/test_image_tobitmap.py | 18 ++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/test/helper.py b/test/helper.py index 8c45858b6..0958c56a9 100644 --- a/test/helper.py +++ b/test/helper.py @@ -129,22 +129,21 @@ py3 = (sys.version_info >= (3, 0)) # success() # else: # failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) -# -# -# # helpers -# -# from io import BytesIO -# -# -# def fromstring(data): -# from PIL import Image -# return Image.open(BytesIO(data)) -# -# -# def tostring(im, format, **options): -# out = BytesIO() -# im.save(out, format, **options) -# return out.getvalue() + + +# helpers + +def fromstring(data): + from io import BytesIO + from PIL import Image + return Image.open(BytesIO(data)) + + +def tostring(im, format, **options): + from io import BytesIO + out = BytesIO() + im.save(out, format, **options) + return out.getvalue() def lena(mode="RGB", cache={}): diff --git a/test/test_image_tobitmap.py b/test/test_image_tobitmap.py index 00e5af0cd..4e2a16df0 100644 --- a/test/test_image_tobitmap.py +++ b/test/test_image_tobitmap.py @@ -1,21 +1,19 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image +from helper import unittest, PillowTestCase, lena, fromstring class TestImage(PillowTestCase): -def test_sanity(self): + def test_sanity(self): - self.assertRaises(ValueError, lambda: lena().tobitmap()) - assert_no_exception(lambda: lena().convert("1").tobitmap()) + self.assertRaises(ValueError, lambda: lena().tobitmap()) + lena().convert("1").tobitmap() - im1 = lena().convert("1") + im1 = lena().convert("1") - bitmap = im1.tobitmap() + bitmap = im1.tobitmap() - assert_true(isinstance(bitmap, bytes)) - assert_image_equal(im1, fromstring(bitmap)) + self.assertIsInstance(bitmap, bytes) + self.assert_image_equal(im1, fromstring(bitmap)) if __name__ == '__main__': From 3fda42d280f13928b7647d97252331dd768b5bd7 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 13:39:45 +0300 Subject: [PATCH 080/488] Convert more tests --- Tests/test_file_fli.py | 14 ---- Tests/test_image_getbbox.py | 36 --------- Tests/test_image_getpixel.py | 49 ------------ Tests/test_imagefont.py | 135 -------------------------------- test/helper.py | 46 +++++------ test/test_file_fli.py | 23 ++++++ test/test_image_getbbox.py | 45 +++++++++++ test/test_image_getpixel.py | 54 +++++++++++++ test/test_imagefont.py | 145 +++++++++++++++++++++++++++++++++++ 9 files changed, 290 insertions(+), 257 deletions(-) delete mode 100644 Tests/test_file_fli.py delete mode 100644 Tests/test_image_getbbox.py delete mode 100644 Tests/test_image_getpixel.py delete mode 100644 Tests/test_imagefont.py create mode 100644 test/test_file_fli.py create mode 100644 test/test_image_getbbox.py create mode 100644 test/test_image_getpixel.py create mode 100644 test/test_imagefont.py diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py deleted file mode 100644 index 4e06a732e..000000000 --- a/Tests/test_file_fli.py +++ /dev/null @@ -1,14 +0,0 @@ -from tester import * - -from PIL import Image - -# sample ppm stream -file = "Images/lena.fli" -data = open(file, "rb").read() - -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "P") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "FLI") diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py deleted file mode 100644 index c0f846169..000000000 --- a/Tests/test_image_getbbox.py +++ /dev/null @@ -1,36 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - - bbox = lena().getbbox() - assert_true(isinstance(bbox, tuple)) - -def test_bbox(): - - # 8-bit mode - im = Image.new("L", (100, 100), 0) - assert_equal(im.getbbox(), None) - - im.paste(255, (10, 25, 90, 75)) - assert_equal(im.getbbox(), (10, 25, 90, 75)) - - im.paste(255, (25, 10, 75, 90)) - assert_equal(im.getbbox(), (10, 10, 90, 90)) - - im.paste(255, (-10, -10, 110, 110)) - assert_equal(im.getbbox(), (0, 0, 100, 100)) - - # 32-bit mode - im = Image.new("RGB", (100, 100), 0) - assert_equal(im.getbbox(), None) - - im.paste(255, (10, 25, 90, 75)) - assert_equal(im.getbbox(), (10, 25, 90, 75)) - - im.paste(255, (25, 10, 75, 90)) - assert_equal(im.getbbox(), (10, 10, 90, 90)) - - im.paste(255, (-10, -10, 110, 110)) - assert_equal(im.getbbox(), (0, 0, 100, 100)) diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py deleted file mode 100644 index da3d8115e..000000000 --- a/Tests/test_image_getpixel.py +++ /dev/null @@ -1,49 +0,0 @@ -from tester import * - -from PIL import Image - -Image.USE_CFFI_ACCESS=False - -def color(mode): - bands = Image.getmodebands(mode) - if bands == 1: - return 1 - else: - return tuple(range(1, bands+1)) - - - -def check(mode, c=None): - if not c: - c = color(mode) - - #check putpixel - im = Image.new(mode, (1, 1), None) - im.putpixel((0, 0), c) - assert_equal(im.getpixel((0, 0)), c, - "put/getpixel roundtrip failed for mode %s, color %s" % - (mode, c)) - - # check inital color - im = Image.new(mode, (1, 1), c) - assert_equal(im.getpixel((0, 0)), c, - "initial color failed for mode %s, color %s " % - (mode, color)) - -def test_basic(): - for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", - "P", "PA", "RGB", "RGBA", "RGBX", "CMYK","YCbCr"): - check(mode) - -def test_signedness(): - # see https://github.com/python-pillow/Pillow/issues/452 - # pixelaccess is using signed int* instead of uint* - for mode in ("I;16", "I;16B"): - check(mode, 2**15-1) - check(mode, 2**15) - check(mode, 2**15+1) - check(mode, 2**16-1) - - - - diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py deleted file mode 100644 index 9ac2cdd89..000000000 --- a/Tests/test_imagefont.py +++ /dev/null @@ -1,135 +0,0 @@ -from tester import * - -from PIL import Image -from io import BytesIO -import os - -try: - from PIL import ImageFont - ImageFont.core.getfont # check if freetype is available -except ImportError: - skip() - -from PIL import ImageDraw - -font_path = "Tests/fonts/FreeMono.ttf" -font_size=20 - -def test_sanity(): - assert_match(ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") - -def test_font_with_name(): - assert_no_exception(lambda: ImageFont.truetype(font_path, font_size)) - assert_no_exception(lambda: _render(font_path)) - _clean() - -def _font_as_bytes(): - with open(font_path, 'rb') as f: - font_bytes = BytesIO(f.read()) - return font_bytes - -def test_font_with_filelike(): - assert_no_exception(lambda: ImageFont.truetype(_font_as_bytes(), font_size)) - assert_no_exception(lambda: _render(_font_as_bytes())) - # Usage note: making two fonts from the same buffer fails. - #shared_bytes = _font_as_bytes() - #assert_no_exception(lambda: _render(shared_bytes)) - #assert_exception(Exception, lambda: _render(shared_bytes)) - _clean() - -def test_font_with_open_file(): - with open(font_path, 'rb') as f: - assert_no_exception(lambda: _render(f)) - _clean() - -def test_font_old_parameters(): - assert_warning(DeprecationWarning, lambda: ImageFont.truetype(filename=font_path, size=font_size)) - -def _render(font): - txt = "Hello World!" - ttf = ImageFont.truetype(font, font_size) - w, h = ttf.getsize(txt) - img = Image.new("RGB", (256, 64), "white") - d = ImageDraw.Draw(img) - d.text((10, 10), txt, font=ttf, fill='black') - - img.save('font.png') - return img - -def _clean(): - os.unlink('font.png') - -def test_render_equal(): - img_path = _render(font_path) - with open(font_path, 'rb') as f: - font_filelike = BytesIO(f.read()) - img_filelike = _render(font_filelike) - - assert_image_equal(img_path, img_filelike) - _clean() - - -def test_render_multiline(): - im = Image.new(mode='RGB', size=(300,100)) - draw = ImageDraw.Draw(im) - ttf = ImageFont.truetype(font_path, font_size) - line_spacing = draw.textsize('A', font=ttf)[1] + 8 - lines = ['hey you', 'you are awesome', 'this looks awkward'] - y = 0 - for line in lines: - draw.text((0, y), line, font=ttf) - y += line_spacing - - - target = 'Tests/images/multiline_text.png' - target_img = Image.open(target) - - # some versions of freetype have different horizontal spacing. - # setting a tight epsilon, I'm showing the original test failure - # at epsilon = ~38. - assert_image_similar(im, target_img,.5) - - -def test_rotated_transposed_font(): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = ImageFont.truetype(font_path, font_size) - - orientation = Image.ROTATE_90 - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - - # Original font - draw.setfont(font) - box_size_a = draw.textsize(word) - - # Rotated font - draw.setfont(transposed_font) - box_size_b = draw.textsize(word) - - # Check (w,h) of box a is (h,w) of box b - assert_equal(box_size_a[0], box_size_b[1]) - assert_equal(box_size_a[1], box_size_b[0]) - - -def test_unrotated_transposed_font(): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = ImageFont.truetype(font_path, font_size) - - orientation = None - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - - # Original font - draw.setfont(font) - box_size_a = draw.textsize(word) - - # Rotated font - draw.setfont(transposed_font) - box_size_b = draw.textsize(word) - - # Check boxes a and b are same size - assert_equal(box_size_a, box_size_b) - - diff --git a/test/helper.py b/test/helper.py index 0958c56a9..e6684b845 100644 --- a/test/helper.py +++ b/test/helper.py @@ -34,6 +34,29 @@ class PillowTestCase(unittest.TestCase): a.tobytes(), b.tobytes(), msg or "got different content") + def assert_image_similar(self, a, b, epsilon, msg=None): + epsilon = float(epsilon) + self.assertEqual( + a.mode, b.mode, + msg or "got mode %r, expected %r" % (a.mode, b.mode)) + self.assertEqual( + a.size, b.size, + msg or "got size %r, expected %r" % (a.size, b.size)) + + diff = 0 + try: + ord(b'0') + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(ord(abyte)-ord(bbyte)) + except: + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(abyte-bbyte) + ave_diff = float(diff)/(a.size[0]*a.size[1]) + self.assertGreaterEqual( + epsilon, ave_diff, + msg or "average pixel value difference %.4f > epsilon %.4f" % ( + ave_diff, epsilon)) + def assert_warning(self, warn_class, func): import warnings @@ -169,29 +192,6 @@ def lena(mode="RGB", cache={}): # success() # # -# def assert_image_similar(a, b, epsilon, msg=None): -# epsilon = float(epsilon) -# if a.mode != b.mode: -# return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) -# elif a.size != b.size: -# return failure(msg or "got size %r, expected %r" % (a.size, b.size)) -# diff = 0 -# try: -# ord(b'0') -# for abyte, bbyte in zip(a.tobytes(), b.tobytes()): -# diff += abs(ord(abyte)-ord(bbyte)) -# except: -# for abyte, bbyte in zip(a.tobytes(), b.tobytes()): -# diff += abs(abyte-bbyte) -# ave_diff = float(diff)/(a.size[0]*a.size[1]) -# if epsilon < ave_diff: -# return failure( -# msg or "average pixel value difference %.4f > epsilon %.4f" % ( -# ave_diff, epsilon)) -# else: -# return success() -# -# # def tempfile(template, *extra): # import os # import os.path diff --git a/test/test_file_fli.py b/test/test_file_fli.py new file mode 100644 index 000000000..dd22a58f9 --- /dev/null +++ b/test/test_file_fli.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + +# sample ppm stream +file = "Images/lena.fli" +data = open(file, "rb").read() + + +class TestImage(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "FLI") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_getbbox.py b/test/test_image_getbbox.py new file mode 100644 index 000000000..83a6a3dec --- /dev/null +++ b/test/test_image_getbbox.py @@ -0,0 +1,45 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImage(PillowTestCase): + + def test_sanity(self): + + bbox = lena().getbbox() + self.assertIsInstance(bbox, tuple) + + def test_bbox(self): + + # 8-bit mode + im = Image.new("L", (100, 100), 0) + self.assertEqual(im.getbbox(), None) + + im.paste(255, (10, 25, 90, 75)) + self.assertEqual(im.getbbox(), (10, 25, 90, 75)) + + im.paste(255, (25, 10, 75, 90)) + self.assertEqual(im.getbbox(), (10, 10, 90, 90)) + + im.paste(255, (-10, -10, 110, 110)) + self.assertEqual(im.getbbox(), (0, 0, 100, 100)) + + # 32-bit mode + im = Image.new("RGB", (100, 100), 0) + self.assertEqual(im.getbbox(), None) + + im.paste(255, (10, 25, 90, 75)) + self.assertEqual(im.getbbox(), (10, 25, 90, 75)) + + im.paste(255, (25, 10, 75, 90)) + self.assertEqual(im.getbbox(), (10, 10, 90, 90)) + + im.paste(255, (-10, -10, 110, 110)) + self.assertEqual(im.getbbox(), (0, 0, 100, 100)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_getpixel.py b/test/test_image_getpixel.py new file mode 100644 index 000000000..de5f185ec --- /dev/null +++ b/test/test_image_getpixel.py @@ -0,0 +1,54 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + +Image.USE_CFFI_ACCESS = False + + +class TestImage(PillowTestCase): + + def color(self, mode): + bands = Image.getmodebands(mode) + if bands == 1: + return 1 + else: + return tuple(range(1, bands+1)) + + def check(self, mode, c=None): + if not c: + c = self.color(mode) + + # check putpixel + im = Image.new(mode, (1, 1), None) + im.putpixel((0, 0), c) + self.assertEqual( + im.getpixel((0, 0)), c, + "put/getpixel roundtrip failed for mode %s, color %s" % + (mode, c)) + + # check inital color + im = Image.new(mode, (1, 1), c) + self.assertEqual( + im.getpixel((0, 0)), c, + "initial color failed for mode %s, color %s " % + (mode, self.color)) + + def test_basic(self): + for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", + "P", "PA", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr"): + self.check(mode) + + def test_signedness(self): + # see https://github.com/python-pillow/Pillow/issues/452 + # pixelaccess is using signed int* instead of uint* + for mode in ("I;16", "I;16B"): + self.check(mode, 2**15-1) + self.check(mode, 2**15) + self.check(mode, 2**15+1) + self.check(mode, 2**16-1) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagefont.py b/test/test_imagefont.py new file mode 100644 index 000000000..17cb38cc2 --- /dev/null +++ b/test/test_imagefont.py @@ -0,0 +1,145 @@ +from helper import unittest, PillowTestCase + +from PIL import Image +from PIL import ImageDraw +from io import BytesIO +import os + +font_path = "Tests/fonts/FreeMono.ttf" +font_size = 20 + + +try: + from PIL import ImageFont + ImageFont.core.getfont # check if freetype is available + + class TestImageFont(PillowTestCase): + + def test_sanity(self): + self.assertRegexpMatches( + ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") + + def test_font_with_name(self): + ImageFont.truetype(font_path, font_size) + self._render(font_path) + self._clean() + + def _font_as_bytes(self): + with open(font_path, 'rb') as f: + font_bytes = BytesIO(f.read()) + return font_bytes + + def test_font_with_filelike(self): + ImageFont.truetype(self._font_as_bytes(), font_size) + self._render(self._font_as_bytes()) + # Usage note: making two fonts from the same buffer fails. + # shared_bytes = self._font_as_bytes() + # self._render(shared_bytes) + # self.assertRaises(Exception, lambda: _render(shared_bytes)) + self._clean() + + def test_font_with_open_file(self): + with open(font_path, 'rb') as f: + self._render(f) + self._clean() + + def test_font_old_parameters(self): + self.assert_warning( + DeprecationWarning, + lambda: ImageFont.truetype(filename=font_path, size=font_size)) + + def _render(self, font): + txt = "Hello World!" + ttf = ImageFont.truetype(font, font_size) + w, h = ttf.getsize(txt) + img = Image.new("RGB", (256, 64), "white") + d = ImageDraw.Draw(img) + d.text((10, 10), txt, font=ttf, fill='black') + + img.save('font.png') + return img + + def _clean(self): + os.unlink('font.png') + + def test_render_equal(self): + img_path = self._render(font_path) + with open(font_path, 'rb') as f: + font_filelike = BytesIO(f.read()) + img_filelike = self._render(font_filelike) + + self.assert_image_equal(img_path, img_filelike) + self._clean() + + def test_render_multiline(self): + im = Image.new(mode='RGB', size=(300, 100)) + draw = ImageDraw.Draw(im) + ttf = ImageFont.truetype(font_path, font_size) + line_spacing = draw.textsize('A', font=ttf)[1] + 8 + lines = ['hey you', 'you are awesome', 'this looks awkward'] + y = 0 + for line in lines: + draw.text((0, y), line, font=ttf) + y += line_spacing + + target = 'Tests/images/multiline_text.png' + target_img = Image.open(target) + + # some versions of freetype have different horizontal spacing. + # setting a tight epsilon, I'm showing the original test failure + # at epsilon = ~38. + self.assert_image_similar(im, target_img, .5) + + def test_rotated_transposed_font(self): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" + font = ImageFont.truetype(font_path, font_size) + + orientation = Image.ROTATE_90 + transposed_font = ImageFont.TransposedFont( + font, orientation=orientation) + + # Original font + draw.setfont(font) + box_size_a = draw.textsize(word) + + # Rotated font + draw.setfont(transposed_font) + box_size_b = draw.textsize(word) + + # Check (w,h) of box a is (h,w) of box b + self.assertEqual(box_size_a[0], box_size_b[1]) + self.assertEqual(box_size_a[1], box_size_b[0]) + + def test_unrotated_transposed_font(self): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" + font = ImageFont.truetype(font_path, font_size) + + orientation = None + transposed_font = ImageFont.TransposedFont( + font, orientation=orientation) + + # Original font + draw.setfont(font) + box_size_a = draw.textsize(word) + + # Rotated font + draw.setfont(transposed_font) + box_size_b = draw.textsize(word) + + # Check boxes a and b are same size + self.assertEqual(box_size_a, box_size_b) + +except ImportError: + class TestImageFont(PillowTestCase): + def test_skip(self): + self.skipTest("ImportError") + + +if __name__ == '__main__': + unittest.main() + +# End of file From 3249d8f3a5728b90ac961f19dc04381d1b390ca2 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 14:01:57 +0300 Subject: [PATCH 081/488] Convert test_image_putpixel.py. More changes needed for test_cffi.py --- Tests/test_cffi.py | 99 ------------------------------ Tests/test_image_putpalette.py | 28 --------- Tests/test_image_putpixel.py | 45 -------------- test/test_cffi.py | 109 +++++++++++++++++++++++++++++++++ test/test_image_putpalette.py | 36 +++++++++++ test/test_image_putpixel.py | 50 +++++++++++++++ 6 files changed, 195 insertions(+), 172 deletions(-) delete mode 100644 Tests/test_cffi.py delete mode 100644 Tests/test_image_putpalette.py delete mode 100644 Tests/test_image_putpixel.py create mode 100644 test/test_cffi.py create mode 100644 test/test_image_putpalette.py create mode 100644 test/test_image_putpixel.py diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py deleted file mode 100644 index 1c0d8d31e..000000000 --- a/Tests/test_cffi.py +++ /dev/null @@ -1,99 +0,0 @@ -from tester import * - -try: - import cffi -except: - skip() - -from PIL import Image, PyAccess - -import test_image_putpixel as put -import test_image_getpixel as get - - -Image.USE_CFFI_ACCESS = True - -def test_put(): - put.test_sanity() - -def test_get(): - get.test_basic() - get.test_signedness() - -def _test_get_access(im): - """ Do we get the same thing as the old pixel access """ - - """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - - w,h = im.size - for x in range(0,w,10): - for y in range(0,h,10): - assert_equal(access[(x,y)], caccess[(x,y)]) - -def test_get_vs_c(): - _test_get_access(lena('RGB')) - _test_get_access(lena('RGBA')) - _test_get_access(lena('L')) - _test_get_access(lena('LA')) - _test_get_access(lena('1')) - _test_get_access(lena('P')) - #_test_get_access(lena('PA')) # PA -- how do I make a PA image??? - _test_get_access(lena('F')) - - im = Image.new('I;16', (10,10), 40000) - _test_get_access(im) - im = Image.new('I;16L', (10,10), 40000) - _test_get_access(im) - im = Image.new('I;16B', (10,10), 40000) - _test_get_access(im) - - im = Image.new('I', (10,10), 40000) - _test_get_access(im) - # These don't actually appear to be modes that I can actually make, - # as unpack sets them directly into the I mode. - #im = Image.new('I;32L', (10,10), -2**10) - #_test_get_access(im) - #im = Image.new('I;32B', (10,10), 2**10) - #_test_get_access(im) - - - -def _test_set_access(im, color): - """ Are we writing the correct bits into the image? """ - - """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - - w,h = im.size - for x in range(0,w,10): - for y in range(0,h,10): - access[(x,y)] = color - assert_equal(color, caccess[(x,y)]) - -def test_set_vs_c(): - _test_set_access(lena('RGB'), (255, 128,0) ) - _test_set_access(lena('RGBA'), (255, 192, 128, 0)) - _test_set_access(lena('L'), 128) - _test_set_access(lena('LA'), (128,128)) - _test_set_access(lena('1'), 255) - _test_set_access(lena('P') , 128) - ##_test_set_access(i, (128,128)) #PA -- undone how to make - _test_set_access(lena('F'), 1024.0) - - im = Image.new('I;16', (10,10), 40000) - _test_set_access(im, 45000) - im = Image.new('I;16L', (10,10), 40000) - _test_set_access(im, 45000) - im = Image.new('I;16B', (10,10), 40000) - _test_set_access(im, 45000) - - - im = Image.new('I', (10,10), 40000) - _test_set_access(im, 45000) -# im = Image.new('I;32L', (10,10), -(2**10)) -# _test_set_access(im, -(2**13)+1) - #im = Image.new('I;32B', (10,10), 2**10) - #_test_set_access(im, 2**13-1) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py deleted file mode 100644 index b7ebb8853..000000000 --- a/Tests/test_image_putpalette.py +++ /dev/null @@ -1,28 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImagePalette - -def test_putpalette(): - def palette(mode): - im = lena(mode).copy() - im.putpalette(list(range(256))*3) - p = im.getpalette() - if p: - return im.mode, p[:10] - return im.mode - assert_exception(ValueError, lambda: palette("1")) - assert_equal(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - assert_equal(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - assert_exception(ValueError, lambda: palette("I")) - assert_exception(ValueError, lambda: palette("F")) - assert_exception(ValueError, lambda: palette("RGB")) - assert_exception(ValueError, lambda: palette("RGBA")) - assert_exception(ValueError, lambda: palette("YCbCr")) - -def test_imagepalette(): - im = lena("P") - assert_no_exception(lambda: im.putpalette(ImagePalette.negative())) - assert_no_exception(lambda: im.putpalette(ImagePalette.random())) - assert_no_exception(lambda: im.putpalette(ImagePalette.sepia())) - assert_no_exception(lambda: im.putpalette(ImagePalette.wedge())) diff --git a/Tests/test_image_putpixel.py b/Tests/test_image_putpixel.py deleted file mode 100644 index 5f19237cb..000000000 --- a/Tests/test_image_putpixel.py +++ /dev/null @@ -1,45 +0,0 @@ -from tester import * - -from PIL import Image - -Image.USE_CFFI_ACCESS=False - -def test_sanity(): - - im1 = lena() - im2 = Image.new(im1.mode, im1.size, 0) - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pos = x, y - im2.putpixel(pos, im1.getpixel(pos)) - - assert_image_equal(im1, im2) - - im2 = Image.new(im1.mode, im1.size, 0) - im2.readonly = 1 - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pos = x, y - im2.putpixel(pos, im1.getpixel(pos)) - - assert_false(im2.readonly) - assert_image_equal(im1, im2) - - im2 = Image.new(im1.mode, im1.size, 0) - - pix1 = im1.load() - pix2 = im2.load() - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pix2[x, y] = pix1[x, y] - - assert_image_equal(im1, im2) - - - - -# see test_image_getpixel for more tests - diff --git a/test/test_cffi.py b/test/test_cffi.py new file mode 100644 index 000000000..1492af32d --- /dev/null +++ b/test/test_cffi.py @@ -0,0 +1,109 @@ +from helper import unittest, PillowTestCase, lena + +try: + import cffi + + from PIL import Image, PyAccess + + import test_image_putpixel as put + import test_image_getpixel as get + + class TestCffi(PillowTestCase): + + Image.USE_CFFI_ACCESS = True + + def test_put(self): + put.test_sanity() + + def test_get(self): + get.test_basic() + get.test_signedness() + + def _test_get_access(self, im): + """ Do we get the same thing as the old pixel access """ + + """ Using private interfaces, forcing a capi access and a pyaccess + for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w, h = im.size + for x in range(0, w, 10): + for y in range(0, h, 10): + self.assertEqual(access[(x, y)], caccess[(x, y)]) + + def test_get_vs_c(self): + self._test_get_access(lena('RGB')) + self._test_get_access(lena('RGBA')) + self._test_get_access(lena('L')) + self._test_get_access(lena('LA')) + self._test_get_access(lena('1')) + self._test_get_access(lena('P')) + # PA -- how do I make a PA image??? + # self._test_get_access(lena('PA')) + self._test_get_access(lena('F')) + + im = Image.new('I;16', (10, 10), 40000) + self._test_get_access(im) + im = Image.new('I;16L', (10, 10), 40000) + self._test_get_access(im) + im = Image.new('I;16B', (10, 10), 40000) + self._test_get_access(im) + + im = Image.new('I', (10, 10), 40000) + self._test_get_access(im) + # These don't actually appear to be modes that I can actually make, + # as unpack sets them directly into the I mode. + # im = Image.new('I;32L', (10, 10), -2**10) + # self._test_get_access(im) + # im = Image.new('I;32B', (10, 10), 2**10) + # self._test_get_access(im) + + def _test_set_access(self, im, color): + """ Are we writing the correct bits into the image? """ + + """ Using private interfaces, forcing a capi access and a pyaccess + for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w, h = im.size + for x in range(0, w, 10): + for y in range(0, h, 10): + access[(x, y)] = color + self.assertEqual(color, caccess[(x, y)]) + + def test_set_vs_c(self): + self._test_set_access(lena('RGB'), (255, 128, 0)) + self._test_set_access(lena('RGBA'), (255, 192, 128, 0)) + self._test_set_access(lena('L'), 128) + self._test_set_access(lena('LA'), (128, 128)) + self._test_set_access(lena('1'), 255) + self._test_set_access(lena('P'), 128) + # self._test_set_access(i, (128, 128)) #PA -- undone how to make + self._test_set_access(lena('F'), 1024.0) + + im = Image.new('I;16', (10, 10), 40000) + self._test_set_access(im, 45000) + im = Image.new('I;16L', (10, 10), 40000) + self._test_set_access(im, 45000) + im = Image.new('I;16B', (10, 10), 40000) + self._test_set_access(im, 45000) + + im = Image.new('I', (10, 10), 40000) + self._test_set_access(im, 45000) + # im = Image.new('I;32L', (10, 10), -(2**10)) + # self._test_set_access(im, -(2**13)+1) + # im = Image.new('I;32B', (10, 10), 2**10) + # self._test_set_access(im, 2**13-1) + +except ImportError: + class TestCffi(PillowTestCase): + def test_skip(self): + self.skipTest("ImportError") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_putpalette.py b/test/test_image_putpalette.py new file mode 100644 index 000000000..b77dcbf00 --- /dev/null +++ b/test/test_image_putpalette.py @@ -0,0 +1,36 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import ImagePalette + + +class TestImage(PillowTestCase): + + def test_putpalette(self): + def palette(mode): + im = lena(mode).copy() + im.putpalette(list(range(256))*3) + p = im.getpalette() + if p: + return im.mode, p[:10] + return im.mode + self.assertRaises(ValueError, lambda: palette("1")) + self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + self.assertRaises(ValueError, lambda: palette("I")) + self.assertRaises(ValueError, lambda: palette("F")) + self.assertRaises(ValueError, lambda: palette("RGB")) + self.assertRaises(ValueError, lambda: palette("RGBA")) + self.assertRaises(ValueError, lambda: palette("YCbCr")) + + def test_imagepalette(self): + im = lena("P") + im.putpalette(ImagePalette.negative()) + im.putpalette(ImagePalette.random()) + im.putpalette(ImagePalette.sepia()) + im.putpalette(ImagePalette.wedge()) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_putpixel.py b/test/test_image_putpixel.py new file mode 100644 index 000000000..a7f5dc2bb --- /dev/null +++ b/test/test_image_putpixel.py @@ -0,0 +1,50 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + +Image.USE_CFFI_ACCESS = False + + +class TestImagePutPixel(PillowTestCase): + + def test_sanity(self): + + im1 = lena() + im2 = Image.new(im1.mode, im1.size, 0) + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + self.assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + im2.readonly = 1 + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + self.assertFalse(im2.readonly) + self.assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + + pix1 = im1.load() + pix2 = im2.load() + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pix2[x, y] = pix1[x, y] + + self.assert_image_equal(im1, im2) + + # see test_image_getpixel for more tests + + +if __name__ == '__main__': + unittest.main() + +# End of file From 2833cb42b45df2025bc408584e8cd8c828f3a6fa Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 14:19:29 +0300 Subject: [PATCH 082/488] Fixes for test_cffi.py --- test/test_cffi.py | 18 +++++++++++------- test/test_image_getpixel.py | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/test_cffi.py b/test/test_cffi.py index 1492af32d..638386ac4 100644 --- a/test/test_cffi.py +++ b/test/test_cffi.py @@ -5,19 +5,23 @@ try: from PIL import Image, PyAccess - import test_image_putpixel as put - import test_image_getpixel as get + from test_image_putpixel import TestImagePutPixel + from test_image_getpixel import TestImageGetPixel - class TestCffi(PillowTestCase): + Image.USE_CFFI_ACCESS = True - Image.USE_CFFI_ACCESS = True + class TestCffiPutPixel(TestImagePutPixel): def test_put(self): - put.test_sanity() + self.test_sanity() + + class TestCffiGetPixel(TestImageGetPixel): def test_get(self): - get.test_basic() - get.test_signedness() + self.test_basic() + self.test_signedness() + + class TestCffi(PillowTestCase): def _test_get_access(self, im): """ Do we get the same thing as the old pixel access """ diff --git a/test/test_image_getpixel.py b/test/test_image_getpixel.py index de5f185ec..9749ba836 100644 --- a/test/test_image_getpixel.py +++ b/test/test_image_getpixel.py @@ -5,7 +5,7 @@ from PIL import Image Image.USE_CFFI_ACCESS = False -class TestImage(PillowTestCase): +class TestImageGetPixel(PillowTestCase): def color(self, mode): bands = Image.getmodebands(mode) From 07aa1a56bbddd79128a81584eb0ec8f903c5192f Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 15:31:22 +0300 Subject: [PATCH 083/488] Save test_cffi for another day --- Tests/test_cffi.py | 99 ++++++++++++++++++++++++++++++ Tests/test_image_getpixel.py | 49 +++++++++++++++ Tests/test_image_putpixel.py | 45 ++++++++++++++ test/test_cffi.py | 113 ----------------------------------- test/test_image_getpixel.py | 54 ----------------- test/test_image_putpixel.py | 50 ---------------- 6 files changed, 193 insertions(+), 217 deletions(-) create mode 100644 Tests/test_cffi.py create mode 100644 Tests/test_image_getpixel.py create mode 100644 Tests/test_image_putpixel.py delete mode 100644 test/test_cffi.py delete mode 100644 test/test_image_getpixel.py delete mode 100644 test/test_image_putpixel.py diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py new file mode 100644 index 000000000..1c0d8d31e --- /dev/null +++ b/Tests/test_cffi.py @@ -0,0 +1,99 @@ +from tester import * + +try: + import cffi +except: + skip() + +from PIL import Image, PyAccess + +import test_image_putpixel as put +import test_image_getpixel as get + + +Image.USE_CFFI_ACCESS = True + +def test_put(): + put.test_sanity() + +def test_get(): + get.test_basic() + get.test_signedness() + +def _test_get_access(im): + """ Do we get the same thing as the old pixel access """ + + """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w,h = im.size + for x in range(0,w,10): + for y in range(0,h,10): + assert_equal(access[(x,y)], caccess[(x,y)]) + +def test_get_vs_c(): + _test_get_access(lena('RGB')) + _test_get_access(lena('RGBA')) + _test_get_access(lena('L')) + _test_get_access(lena('LA')) + _test_get_access(lena('1')) + _test_get_access(lena('P')) + #_test_get_access(lena('PA')) # PA -- how do I make a PA image??? + _test_get_access(lena('F')) + + im = Image.new('I;16', (10,10), 40000) + _test_get_access(im) + im = Image.new('I;16L', (10,10), 40000) + _test_get_access(im) + im = Image.new('I;16B', (10,10), 40000) + _test_get_access(im) + + im = Image.new('I', (10,10), 40000) + _test_get_access(im) + # These don't actually appear to be modes that I can actually make, + # as unpack sets them directly into the I mode. + #im = Image.new('I;32L', (10,10), -2**10) + #_test_get_access(im) + #im = Image.new('I;32B', (10,10), 2**10) + #_test_get_access(im) + + + +def _test_set_access(im, color): + """ Are we writing the correct bits into the image? """ + + """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w,h = im.size + for x in range(0,w,10): + for y in range(0,h,10): + access[(x,y)] = color + assert_equal(color, caccess[(x,y)]) + +def test_set_vs_c(): + _test_set_access(lena('RGB'), (255, 128,0) ) + _test_set_access(lena('RGBA'), (255, 192, 128, 0)) + _test_set_access(lena('L'), 128) + _test_set_access(lena('LA'), (128,128)) + _test_set_access(lena('1'), 255) + _test_set_access(lena('P') , 128) + ##_test_set_access(i, (128,128)) #PA -- undone how to make + _test_set_access(lena('F'), 1024.0) + + im = Image.new('I;16', (10,10), 40000) + _test_set_access(im, 45000) + im = Image.new('I;16L', (10,10), 40000) + _test_set_access(im, 45000) + im = Image.new('I;16B', (10,10), 40000) + _test_set_access(im, 45000) + + + im = Image.new('I', (10,10), 40000) + _test_set_access(im, 45000) +# im = Image.new('I;32L', (10,10), -(2**10)) +# _test_set_access(im, -(2**13)+1) + #im = Image.new('I;32B', (10,10), 2**10) + #_test_set_access(im, 2**13-1) diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py new file mode 100644 index 000000000..da3d8115e --- /dev/null +++ b/Tests/test_image_getpixel.py @@ -0,0 +1,49 @@ +from tester import * + +from PIL import Image + +Image.USE_CFFI_ACCESS=False + +def color(mode): + bands = Image.getmodebands(mode) + if bands == 1: + return 1 + else: + return tuple(range(1, bands+1)) + + + +def check(mode, c=None): + if not c: + c = color(mode) + + #check putpixel + im = Image.new(mode, (1, 1), None) + im.putpixel((0, 0), c) + assert_equal(im.getpixel((0, 0)), c, + "put/getpixel roundtrip failed for mode %s, color %s" % + (mode, c)) + + # check inital color + im = Image.new(mode, (1, 1), c) + assert_equal(im.getpixel((0, 0)), c, + "initial color failed for mode %s, color %s " % + (mode, color)) + +def test_basic(): + for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", + "P", "PA", "RGB", "RGBA", "RGBX", "CMYK","YCbCr"): + check(mode) + +def test_signedness(): + # see https://github.com/python-pillow/Pillow/issues/452 + # pixelaccess is using signed int* instead of uint* + for mode in ("I;16", "I;16B"): + check(mode, 2**15-1) + check(mode, 2**15) + check(mode, 2**15+1) + check(mode, 2**16-1) + + + + diff --git a/Tests/test_image_putpixel.py b/Tests/test_image_putpixel.py new file mode 100644 index 000000000..5f19237cb --- /dev/null +++ b/Tests/test_image_putpixel.py @@ -0,0 +1,45 @@ +from tester import * + +from PIL import Image + +Image.USE_CFFI_ACCESS=False + +def test_sanity(): + + im1 = lena() + im2 = Image.new(im1.mode, im1.size, 0) + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + im2.readonly = 1 + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + assert_false(im2.readonly) + assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + + pix1 = im1.load() + pix2 = im2.load() + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pix2[x, y] = pix1[x, y] + + assert_image_equal(im1, im2) + + + + +# see test_image_getpixel for more tests + diff --git a/test/test_cffi.py b/test/test_cffi.py deleted file mode 100644 index 638386ac4..000000000 --- a/test/test_cffi.py +++ /dev/null @@ -1,113 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -try: - import cffi - - from PIL import Image, PyAccess - - from test_image_putpixel import TestImagePutPixel - from test_image_getpixel import TestImageGetPixel - - Image.USE_CFFI_ACCESS = True - - class TestCffiPutPixel(TestImagePutPixel): - - def test_put(self): - self.test_sanity() - - class TestCffiGetPixel(TestImageGetPixel): - - def test_get(self): - self.test_basic() - self.test_signedness() - - class TestCffi(PillowTestCase): - - def _test_get_access(self, im): - """ Do we get the same thing as the old pixel access """ - - """ Using private interfaces, forcing a capi access and a pyaccess - for the same image """ - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - - w, h = im.size - for x in range(0, w, 10): - for y in range(0, h, 10): - self.assertEqual(access[(x, y)], caccess[(x, y)]) - - def test_get_vs_c(self): - self._test_get_access(lena('RGB')) - self._test_get_access(lena('RGBA')) - self._test_get_access(lena('L')) - self._test_get_access(lena('LA')) - self._test_get_access(lena('1')) - self._test_get_access(lena('P')) - # PA -- how do I make a PA image??? - # self._test_get_access(lena('PA')) - self._test_get_access(lena('F')) - - im = Image.new('I;16', (10, 10), 40000) - self._test_get_access(im) - im = Image.new('I;16L', (10, 10), 40000) - self._test_get_access(im) - im = Image.new('I;16B', (10, 10), 40000) - self._test_get_access(im) - - im = Image.new('I', (10, 10), 40000) - self._test_get_access(im) - # These don't actually appear to be modes that I can actually make, - # as unpack sets them directly into the I mode. - # im = Image.new('I;32L', (10, 10), -2**10) - # self._test_get_access(im) - # im = Image.new('I;32B', (10, 10), 2**10) - # self._test_get_access(im) - - def _test_set_access(self, im, color): - """ Are we writing the correct bits into the image? """ - - """ Using private interfaces, forcing a capi access and a pyaccess - for the same image """ - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - - w, h = im.size - for x in range(0, w, 10): - for y in range(0, h, 10): - access[(x, y)] = color - self.assertEqual(color, caccess[(x, y)]) - - def test_set_vs_c(self): - self._test_set_access(lena('RGB'), (255, 128, 0)) - self._test_set_access(lena('RGBA'), (255, 192, 128, 0)) - self._test_set_access(lena('L'), 128) - self._test_set_access(lena('LA'), (128, 128)) - self._test_set_access(lena('1'), 255) - self._test_set_access(lena('P'), 128) - # self._test_set_access(i, (128, 128)) #PA -- undone how to make - self._test_set_access(lena('F'), 1024.0) - - im = Image.new('I;16', (10, 10), 40000) - self._test_set_access(im, 45000) - im = Image.new('I;16L', (10, 10), 40000) - self._test_set_access(im, 45000) - im = Image.new('I;16B', (10, 10), 40000) - self._test_set_access(im, 45000) - - im = Image.new('I', (10, 10), 40000) - self._test_set_access(im, 45000) - # im = Image.new('I;32L', (10, 10), -(2**10)) - # self._test_set_access(im, -(2**13)+1) - # im = Image.new('I;32B', (10, 10), 2**10) - # self._test_set_access(im, 2**13-1) - -except ImportError: - class TestCffi(PillowTestCase): - def test_skip(self): - self.skipTest("ImportError") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getpixel.py b/test/test_image_getpixel.py deleted file mode 100644 index 9749ba836..000000000 --- a/test/test_image_getpixel.py +++ /dev/null @@ -1,54 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - -Image.USE_CFFI_ACCESS = False - - -class TestImageGetPixel(PillowTestCase): - - def color(self, mode): - bands = Image.getmodebands(mode) - if bands == 1: - return 1 - else: - return tuple(range(1, bands+1)) - - def check(self, mode, c=None): - if not c: - c = self.color(mode) - - # check putpixel - im = Image.new(mode, (1, 1), None) - im.putpixel((0, 0), c) - self.assertEqual( - im.getpixel((0, 0)), c, - "put/getpixel roundtrip failed for mode %s, color %s" % - (mode, c)) - - # check inital color - im = Image.new(mode, (1, 1), c) - self.assertEqual( - im.getpixel((0, 0)), c, - "initial color failed for mode %s, color %s " % - (mode, self.color)) - - def test_basic(self): - for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", - "P", "PA", "RGB", "RGBA", "RGBX", "CMYK", "YCbCr"): - self.check(mode) - - def test_signedness(self): - # see https://github.com/python-pillow/Pillow/issues/452 - # pixelaccess is using signed int* instead of uint* - for mode in ("I;16", "I;16B"): - self.check(mode, 2**15-1) - self.check(mode, 2**15) - self.check(mode, 2**15+1) - self.check(mode, 2**16-1) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_putpixel.py b/test/test_image_putpixel.py deleted file mode 100644 index a7f5dc2bb..000000000 --- a/test/test_image_putpixel.py +++ /dev/null @@ -1,50 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - -Image.USE_CFFI_ACCESS = False - - -class TestImagePutPixel(PillowTestCase): - - def test_sanity(self): - - im1 = lena() - im2 = Image.new(im1.mode, im1.size, 0) - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pos = x, y - im2.putpixel(pos, im1.getpixel(pos)) - - self.assert_image_equal(im1, im2) - - im2 = Image.new(im1.mode, im1.size, 0) - im2.readonly = 1 - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pos = x, y - im2.putpixel(pos, im1.getpixel(pos)) - - self.assertFalse(im2.readonly) - self.assert_image_equal(im1, im2) - - im2 = Image.new(im1.mode, im1.size, 0) - - pix1 = im1.load() - pix2 = im2.load() - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pix2[x, y] = pix1[x, y] - - self.assert_image_equal(im1, im2) - - # see test_image_getpixel for more tests - - -if __name__ == '__main__': - unittest.main() - -# End of file From 8a870c0ee9af404685c3cbf2787169b00d99d397 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 16:13:58 +0300 Subject: [PATCH 084/488] Convert more tests --- Tests/test_bmp_reference.py | 86 -------------------------------- Tests/test_file_psd.py | 14 ------ Tests/test_image_getcolors.py | 64 ------------------------ Tests/test_image_rotate.py | 15 ------ Tests/test_image_thumbnail.py | 36 -------------- Tests/test_imageops.py | 81 ------------------------------ test/mini.py | 5 ++ test/test_bmp_reference.py | 94 +++++++++++++++++++++++++++++++++++ test/test_file_psd.py | 23 +++++++++ test/test_image_getcolors.py | 74 +++++++++++++++++++++++++++ test/test_image_rotate.py | 22 ++++++++ test/test_image_thumbnail.py | 43 ++++++++++++++++ test/test_imageops.py | 85 +++++++++++++++++++++++++++++++ 13 files changed, 346 insertions(+), 296 deletions(-) delete mode 100644 Tests/test_bmp_reference.py delete mode 100644 Tests/test_file_psd.py delete mode 100644 Tests/test_image_getcolors.py delete mode 100644 Tests/test_image_rotate.py delete mode 100644 Tests/test_image_thumbnail.py delete mode 100644 Tests/test_imageops.py create mode 100644 test/mini.py create mode 100644 test/test_bmp_reference.py create mode 100644 test/test_file_psd.py create mode 100644 test/test_image_getcolors.py create mode 100644 test/test_image_rotate.py create mode 100644 test/test_image_thumbnail.py create mode 100644 test/test_imageops.py diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py deleted file mode 100644 index 99818229f..000000000 --- a/Tests/test_bmp_reference.py +++ /dev/null @@ -1,86 +0,0 @@ -from tester import * - -from PIL import Image -import os - -base = os.path.join('Tests', 'images', 'bmp') - - -def get_files(d, ext='.bmp'): - return [os.path.join(base,d,f) for f - in os.listdir(os.path.join(base, d)) if ext in f] - -def test_bad(): - """ These shouldn't crash/dos, but they shouldn't return anything either """ - for f in get_files('b'): - try: - im = Image.open(f) - im.load() - except Exception as msg: - pass - # print ("Bad Image %s: %s" %(f,msg)) - -def test_questionable(): - """ These shouldn't crash/dos, but its not well defined that these are in spec """ - for f in get_files('q'): - try: - im = Image.open(f) - im.load() - except Exception as msg: - pass - # print ("Bad Image %s: %s" %(f,msg)) - - -def test_good(): - """ These should all work. There's a set of target files in the - html directory that we can compare against. """ - - # Target files, if they're not just replacing the extension - file_map = { 'pal1wb.bmp': 'pal1.png', - 'pal4rle.bmp': 'pal4.png', - 'pal8-0.bmp': 'pal8.png', - 'pal8rle.bmp': 'pal8.png', - 'pal8topdown.bmp': 'pal8.png', - 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', - 'pal8os2.bmp': 'pal8.png', - 'pal8os2sp.bmp': 'pal8.png', - 'pal8os2v2.bmp': 'pal8.png', - 'pal8os2v2-16.bmp': 'pal8.png', - 'pal8v4.bmp': 'pal8.png', - 'pal8v5.bmp': 'pal8.png', - 'rgb16-565pal.bmp': 'rgb16-565.png', - 'rgb24pal.bmp': 'rgb24.png', - 'rgb32.bmp': 'rgb24.png', - 'rgb32bf.bmp': 'rgb24.png' - } - - def get_compare(f): - (head, name) = os.path.split(f) - if name in file_map: - return os.path.join(base, 'html', file_map[name]) - (name,ext) = os.path.splitext(name) - return os.path.join(base, 'html', "%s.png"%name) - - for f in get_files('g'): - try: - im = Image.open(f) - im.load() - compare = Image.open(get_compare(f)) - compare.load() - if im.mode == 'P': - # assert image similar doesn't really work - # with paletized image, since the palette might - # be differently ordered for an equivalent image. - im = im.convert('RGBA') - compare = im.convert('RGBA') - assert_image_similar(im, compare,5) - - - except Exception as msg: - # there are three here that are unsupported: - unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), - os.path.join(base, 'g', 'pal8rle.bmp'), - os.path.join(base, 'g', 'pal4rle.bmp')) - if f not in unsupported: - assert_true(False, "Unsupported Image %s: %s" %(f,msg)) - diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py deleted file mode 100644 index ef2d40594..000000000 --- a/Tests/test_file_psd.py +++ /dev/null @@ -1,14 +0,0 @@ -from tester import * - -from PIL import Image - -# sample ppm stream -file = "Images/lena.psd" -data = open(file, "rb").read() - -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "PSD") diff --git a/Tests/test_image_getcolors.py b/Tests/test_image_getcolors.py deleted file mode 100644 index 2429f9350..000000000 --- a/Tests/test_image_getcolors.py +++ /dev/null @@ -1,64 +0,0 @@ -from tester import * - -from PIL import Image - -def test_getcolors(): - - def getcolors(mode, limit=None): - im = lena(mode) - if limit: - colors = im.getcolors(limit) - else: - colors = im.getcolors() - if colors: - return len(colors) - return None - - assert_equal(getcolors("1"), 2) - assert_equal(getcolors("L"), 193) - assert_equal(getcolors("I"), 193) - assert_equal(getcolors("F"), 193) - assert_equal(getcolors("P"), 54) # fixed palette - assert_equal(getcolors("RGB"), None) - assert_equal(getcolors("RGBA"), None) - assert_equal(getcolors("CMYK"), None) - assert_equal(getcolors("YCbCr"), None) - - assert_equal(getcolors("L", 128), None) - assert_equal(getcolors("L", 1024), 193) - - assert_equal(getcolors("RGB", 8192), None) - assert_equal(getcolors("RGB", 16384), 14836) - assert_equal(getcolors("RGB", 100000), 14836) - - assert_equal(getcolors("RGBA", 16384), 14836) - assert_equal(getcolors("CMYK", 16384), 14836) - assert_equal(getcolors("YCbCr", 16384), 11995) - -# -------------------------------------------------------------------- - -def test_pack(): - # Pack problems for small tables (@PIL209) - - im = lena().quantize(3).convert("RGB") - - expected = [(3236, (227, 183, 147)), (6297, (143, 84, 81)), (6851, (208, 143, 112))] - - A = im.getcolors(maxcolors=2) - assert_equal(A, None) - - A = im.getcolors(maxcolors=3) - A.sort() - assert_equal(A, expected) - - A = im.getcolors(maxcolors=4) - A.sort() - assert_equal(A, expected) - - A = im.getcolors(maxcolors=8) - A.sort() - assert_equal(A, expected) - - A = im.getcolors(maxcolors=16) - A.sort() - assert_equal(A, expected) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py deleted file mode 100644 index 5e4782c87..000000000 --- a/Tests/test_image_rotate.py +++ /dev/null @@ -1,15 +0,0 @@ -from tester import * - -from PIL import Image - -def test_rotate(): - def rotate(mode): - im = lena(mode) - out = im.rotate(45) - assert_equal(out.mode, mode) - assert_equal(out.size, im.size) # default rotate clips output - out = im.rotate(45, expand=1) - assert_equal(out.mode, mode) - assert_true(out.size != im.size) - for mode in "1", "P", "L", "RGB", "I", "F": - yield_test(rotate, mode) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py deleted file mode 100644 index 871dd1f54..000000000 --- a/Tests/test_image_thumbnail.py +++ /dev/null @@ -1,36 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - - im = lena() - im.thumbnail((100, 100)) - - assert_image(im, im.mode, (100, 100)) - -def test_aspect(): - - im = lena() - im.thumbnail((100, 100)) - assert_image(im, im.mode, (100, 100)) - - im = lena().resize((128, 256)) - im.thumbnail((100, 100)) - assert_image(im, im.mode, (50, 100)) - - im = lena().resize((128, 256)) - im.thumbnail((50, 100)) - assert_image(im, im.mode, (50, 100)) - - im = lena().resize((256, 128)) - im.thumbnail((100, 100)) - assert_image(im, im.mode, (100, 50)) - - im = lena().resize((256, 128)) - im.thumbnail((100, 50)) - assert_image(im, im.mode, (100, 50)) - - im = lena().resize((128, 128)) - im.thumbnail((100, 100)) - assert_image(im, im.mode, (100, 100)) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py deleted file mode 100644 index 8ed5ccefa..000000000 --- a/Tests/test_imageops.py +++ /dev/null @@ -1,81 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageOps - -class Deformer: - def getmesh(self, im): - x, y = im.size - return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] - -deformer = Deformer() - -def test_sanity(): - - ImageOps.autocontrast(lena("L")) - ImageOps.autocontrast(lena("RGB")) - - ImageOps.autocontrast(lena("L"), cutoff=10) - ImageOps.autocontrast(lena("L"), ignore=[0, 255]) - - ImageOps.colorize(lena("L"), (0, 0, 0), (255, 255, 255)) - ImageOps.colorize(lena("L"), "black", "white") - - ImageOps.crop(lena("L"), 1) - ImageOps.crop(lena("RGB"), 1) - - ImageOps.deform(lena("L"), deformer) - ImageOps.deform(lena("RGB"), deformer) - - ImageOps.equalize(lena("L")) - ImageOps.equalize(lena("RGB")) - - ImageOps.expand(lena("L"), 1) - ImageOps.expand(lena("RGB"), 1) - ImageOps.expand(lena("L"), 2, "blue") - ImageOps.expand(lena("RGB"), 2, "blue") - - ImageOps.fit(lena("L"), (128, 128)) - ImageOps.fit(lena("RGB"), (128, 128)) - - ImageOps.flip(lena("L")) - ImageOps.flip(lena("RGB")) - - ImageOps.grayscale(lena("L")) - ImageOps.grayscale(lena("RGB")) - - ImageOps.invert(lena("L")) - ImageOps.invert(lena("RGB")) - - ImageOps.mirror(lena("L")) - ImageOps.mirror(lena("RGB")) - - ImageOps.posterize(lena("L"), 4) - ImageOps.posterize(lena("RGB"), 4) - - ImageOps.solarize(lena("L")) - ImageOps.solarize(lena("RGB")) - - success() - -def test_1pxfit(): - # Division by zero in equalize if image is 1 pixel high - newimg = ImageOps.fit(lena("RGB").resize((1,1)), (35,35)) - assert_equal(newimg.size,(35,35)) - - newimg = ImageOps.fit(lena("RGB").resize((1,100)), (35,35)) - assert_equal(newimg.size,(35,35)) - - newimg = ImageOps.fit(lena("RGB").resize((100,1)), (35,35)) - assert_equal(newimg.size,(35,35)) - -def test_pil163(): - # Division by zero in equalize if < 255 pixels in image (@PIL163) - - i = lena("RGB").resize((15, 16)) - - ImageOps.equalize(i.convert("L")) - ImageOps.equalize(i.convert("P")) - ImageOps.equalize(i.convert("RGB")) - - success() diff --git a/test/mini.py b/test/mini.py new file mode 100644 index 000000000..c9f1665ab --- /dev/null +++ b/test/mini.py @@ -0,0 +1,5 @@ +from test_image_putpixel import TestImagePutPixel as put +from test_image_getpixel import TestImageGetPixel as get + +dir(get) +get.test_basic() \ No newline at end of file diff --git a/test/test_bmp_reference.py b/test/test_bmp_reference.py new file mode 100644 index 000000000..ed012e7c8 --- /dev/null +++ b/test/test_bmp_reference.py @@ -0,0 +1,94 @@ +from helper import unittest, PillowTestCase + +from PIL import Image +import os + +base = os.path.join('Tests', 'images', 'bmp') + + +class TestImage(PillowTestCase): + + def get_files(self, d, ext='.bmp'): + return [os.path.join(base, d, f) for f + in os.listdir(os.path.join(base, d)) if ext in f] + + def test_bad(self): + """ These shouldn't crash/dos, but they shouldn't return anything + either """ + for f in self.get_files('b'): + try: + im = Image.open(f) + im.load() + except Exception: # as msg: + pass + # print ("Bad Image %s: %s" %(f,msg)) + + def test_questionable(self): + """ These shouldn't crash/dos, but its not well defined that these + are in spec """ + for f in self.get_files('q'): + try: + im = Image.open(f) + im.load() + except Exception: # as msg: + pass + # print ("Bad Image %s: %s" %(f,msg)) + + def test_good(self): + """ These should all work. There's a set of target files in the + html directory that we can compare against. """ + + # Target files, if they're not just replacing the extension + file_map = {'pal1wb.bmp': 'pal1.png', + 'pal4rle.bmp': 'pal4.png', + 'pal8-0.bmp': 'pal8.png', + 'pal8rle.bmp': 'pal8.png', + 'pal8topdown.bmp': 'pal8.png', + 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', + 'pal8os2.bmp': 'pal8.png', + 'pal8os2sp.bmp': 'pal8.png', + 'pal8os2v2.bmp': 'pal8.png', + 'pal8os2v2-16.bmp': 'pal8.png', + 'pal8v4.bmp': 'pal8.png', + 'pal8v5.bmp': 'pal8.png', + 'rgb16-565pal.bmp': 'rgb16-565.png', + 'rgb24pal.bmp': 'rgb24.png', + 'rgb32.bmp': 'rgb24.png', + 'rgb32bf.bmp': 'rgb24.png' + } + + def get_compare(f): + (head, name) = os.path.split(f) + if name in file_map: + return os.path.join(base, 'html', file_map[name]) + (name, ext) = os.path.splitext(name) + return os.path.join(base, 'html', "%s.png" % name) + + for f in self.get_files('g'): + try: + im = Image.open(f) + im.load() + compare = Image.open(get_compare(f)) + compare.load() + if im.mode == 'P': + # assert image similar doesn't really work + # with paletized image, since the palette might + # be differently ordered for an equivalent image. + im = im.convert('RGBA') + compare = im.convert('RGBA') + self.assert_image_similar(im, compare, 5) + + except Exception as msg: + # there are three here that are unsupported: + unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), + os.path.join(base, 'g', 'pal8rle.bmp'), + os.path.join(base, 'g', 'pal4rle.bmp')) + if f not in unsupported: + self.assertTrue( + False, "Unsupported Image %s: %s" % (f, msg)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_file_psd.py b/test/test_file_psd.py new file mode 100644 index 000000000..de3d6f33d --- /dev/null +++ b/test/test_file_psd.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + +# sample ppm stream +file = "Images/lena.psd" +data = open(file, "rb").read() + + +class TestImagePsd(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PSD") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_getcolors.py b/test/test_image_getcolors.py new file mode 100644 index 000000000..cfc827b28 --- /dev/null +++ b/test/test_image_getcolors.py @@ -0,0 +1,74 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImage(PillowTestCase): + + def test_getcolors(self): + + def getcolors(mode, limit=None): + im = lena(mode) + if limit: + colors = im.getcolors(limit) + else: + colors = im.getcolors() + if colors: + return len(colors) + return None + + self.assertEqual(getcolors("1"), 2) + self.assertEqual(getcolors("L"), 193) + self.assertEqual(getcolors("I"), 193) + self.assertEqual(getcolors("F"), 193) + self.assertEqual(getcolors("P"), 54) # fixed palette + self.assertEqual(getcolors("RGB"), None) + self.assertEqual(getcolors("RGBA"), None) + self.assertEqual(getcolors("CMYK"), None) + self.assertEqual(getcolors("YCbCr"), None) + + self.assertEqual(getcolors("L", 128), None) + self.assertEqual(getcolors("L", 1024), 193) + + self.assertEqual(getcolors("RGB", 8192), None) + self.assertEqual(getcolors("RGB", 16384), 14836) + self.assertEqual(getcolors("RGB", 100000), 14836) + + self.assertEqual(getcolors("RGBA", 16384), 14836) + self.assertEqual(getcolors("CMYK", 16384), 14836) + self.assertEqual(getcolors("YCbCr", 16384), 11995) + + # -------------------------------------------------------------------- + + def test_pack(self): + # Pack problems for small tables (@PIL209) + + im = lena().quantize(3).convert("RGB") + + expected = [ + (3236, (227, 183, 147)), + (6297, (143, 84, 81)), + (6851, (208, 143, 112))] + + A = im.getcolors(maxcolors=2) + self.assertEqual(A, None) + + A = im.getcolors(maxcolors=3) + A.sort() + self.assertEqual(A, expected) + + A = im.getcolors(maxcolors=4) + A.sort() + self.assertEqual(A, expected) + + A = im.getcolors(maxcolors=8) + A.sort() + self.assertEqual(A, expected) + + A = im.getcolors(maxcolors=16) + A.sort() + self.assertEqual(A, expected) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_rotate.py b/test/test_image_rotate.py new file mode 100644 index 000000000..531fdd63f --- /dev/null +++ b/test/test_image_rotate.py @@ -0,0 +1,22 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImageRotate(PillowTestCase): + + def test_rotate(self): + def rotate(mode): + im = lena(mode) + out = im.rotate(45) + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, im.size) # default rotate clips output + out = im.rotate(45, expand=1) + self.assertEqual(out.mode, mode) + self.assertNotEqual(out.size, im.size) + for mode in "1", "P", "L", "RGB", "I", "F": + rotate(mode) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_thumbnail.py b/test/test_image_thumbnail.py new file mode 100644 index 000000000..ee49be43e --- /dev/null +++ b/test/test_image_thumbnail.py @@ -0,0 +1,43 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImageThumbnail(PillowTestCase): + + def test_sanity(self): + + im = lena() + im.thumbnail((100, 100)) + + self.assert_image(im, im.mode, (100, 100)) + + def test_aspect(self): + + im = lena() + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (100, 100)) + + im = lena().resize((128, 256)) + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (50, 100)) + + im = lena().resize((128, 256)) + im.thumbnail((50, 100)) + self.assert_image(im, im.mode, (50, 100)) + + im = lena().resize((256, 128)) + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (100, 50)) + + im = lena().resize((256, 128)) + im.thumbnail((100, 50)) + self.assert_image(im, im.mode, (100, 50)) + + im = lena().resize((128, 128)) + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (100, 100)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imageops.py b/test/test_imageops.py new file mode 100644 index 000000000..a4a94ca4d --- /dev/null +++ b/test/test_imageops.py @@ -0,0 +1,85 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import ImageOps + + +class TestImageOps(PillowTestCase): + + class Deformer: + def getmesh(self, im): + x, y = im.size + return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] + + deformer = Deformer() + + def test_sanity(self): + + ImageOps.autocontrast(lena("L")) + ImageOps.autocontrast(lena("RGB")) + + ImageOps.autocontrast(lena("L"), cutoff=10) + ImageOps.autocontrast(lena("L"), ignore=[0, 255]) + + ImageOps.colorize(lena("L"), (0, 0, 0), (255, 255, 255)) + ImageOps.colorize(lena("L"), "black", "white") + + ImageOps.crop(lena("L"), 1) + ImageOps.crop(lena("RGB"), 1) + + ImageOps.deform(lena("L"), self.deformer) + ImageOps.deform(lena("RGB"), self.deformer) + + ImageOps.equalize(lena("L")) + ImageOps.equalize(lena("RGB")) + + ImageOps.expand(lena("L"), 1) + ImageOps.expand(lena("RGB"), 1) + ImageOps.expand(lena("L"), 2, "blue") + ImageOps.expand(lena("RGB"), 2, "blue") + + ImageOps.fit(lena("L"), (128, 128)) + ImageOps.fit(lena("RGB"), (128, 128)) + + ImageOps.flip(lena("L")) + ImageOps.flip(lena("RGB")) + + ImageOps.grayscale(lena("L")) + ImageOps.grayscale(lena("RGB")) + + ImageOps.invert(lena("L")) + ImageOps.invert(lena("RGB")) + + ImageOps.mirror(lena("L")) + ImageOps.mirror(lena("RGB")) + + ImageOps.posterize(lena("L"), 4) + ImageOps.posterize(lena("RGB"), 4) + + ImageOps.solarize(lena("L")) + ImageOps.solarize(lena("RGB")) + + def test_1pxfit(self): + # Division by zero in equalize if image is 1 pixel high + newimg = ImageOps.fit(lena("RGB").resize((1, 1)), (35, 35)) + self.assertEqual(newimg.size, (35, 35)) + + newimg = ImageOps.fit(lena("RGB").resize((1, 100)), (35, 35)) + self.assertEqual(newimg.size, (35, 35)) + + newimg = ImageOps.fit(lena("RGB").resize((100, 1)), (35, 35)) + self.assertEqual(newimg.size, (35, 35)) + + def test_pil163(self): + # Division by zero in equalize if < 255 pixels in image (@PIL163) + + i = lena("RGB").resize((15, 16)) + + ImageOps.equalize(i.convert("L")) + ImageOps.equalize(i.convert("P")) + ImageOps.equalize(i.convert("RGB")) + + +if __name__ == '__main__': + unittest.main() + +# End of file From d18c406d394bc87737f9136280e45d36d31988fe Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 17:27:03 +0300 Subject: [PATCH 085/488] Convert test_imagecolor.py --- Tests/test_imagecolor.py | 54 ------------------------------ test/test_imagecolor.py | 71 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 54 deletions(-) delete mode 100644 Tests/test_imagecolor.py create mode 100644 test/test_imagecolor.py diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py deleted file mode 100644 index c67c20255..000000000 --- a/Tests/test_imagecolor.py +++ /dev/null @@ -1,54 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageColor - -# -------------------------------------------------------------------- -# sanity - -assert_equal((255, 0, 0), ImageColor.getrgb("#f00")) -assert_equal((255, 0, 0), ImageColor.getrgb("#ff0000")) -assert_equal((255, 0, 0), ImageColor.getrgb("rgb(255,0,0)")) -assert_equal((255, 0, 0), ImageColor.getrgb("rgb(255, 0, 0)")) -assert_equal((255, 0, 0), ImageColor.getrgb("rgb(100%,0%,0%)")) -assert_equal((255, 0, 0), ImageColor.getrgb("hsl(0, 100%, 50%)")) -assert_equal((255, 0, 0, 0), ImageColor.getrgb("rgba(255,0,0,0)")) -assert_equal((255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) -assert_equal((255, 0, 0), ImageColor.getrgb("red")) - -# -------------------------------------------------------------------- -# look for rounding errors (based on code by Tim Hatch) - -for color in list(ImageColor.colormap.keys()): - expected = Image.new("RGB", (1, 1), color).convert("L").getpixel((0, 0)) - actual = Image.new("L", (1, 1), color).getpixel((0, 0)) - assert_equal(expected, actual) - -assert_equal((0, 0, 0), ImageColor.getcolor("black", "RGB")) -assert_equal((255, 255, 255), ImageColor.getcolor("white", "RGB")) -assert_equal((0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) -Image.new("RGB", (1, 1), "white") - -assert_equal((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) -assert_equal((255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) -assert_equal((0, 255, 115, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) -Image.new("RGBA", (1, 1), "white") - -assert_equal(0, ImageColor.getcolor("black", "L")) -assert_equal(255, ImageColor.getcolor("white", "L")) -assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) -Image.new("L", (1, 1), "white") - -assert_equal(0, ImageColor.getcolor("black", "1")) -assert_equal(255, ImageColor.getcolor("white", "1")) -# The following test is wrong, but is current behavior -# The correct result should be 255 due to the mode 1 -assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) -# Correct behavior -# assert_equal(255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) -Image.new("1", (1, 1), "white") - -assert_equal((0, 255), ImageColor.getcolor("black", "LA")) -assert_equal((255, 255), ImageColor.getcolor("white", "LA")) -assert_equal((162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) -Image.new("LA", (1, 1), "white") diff --git a/test/test_imagecolor.py b/test/test_imagecolor.py new file mode 100644 index 000000000..5d8944852 --- /dev/null +++ b/test/test_imagecolor.py @@ -0,0 +1,71 @@ +from helper import unittest, PillowTestCase + +from PIL import Image +from PIL import ImageColor + + +class TestImageColor(PillowTestCase): + + def test_sanity(self): + self.assertEqual((255, 0, 0), ImageColor.getrgb("#f00")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("#ff0000")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(255,0,0)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(255, 0, 0)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(100%,0%,0%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(0, 100%, 50%)")) + self.assertEqual((255, 0, 0, 0), ImageColor.getrgb("rgba(255,0,0,0)")) + self.assertEqual( + (255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("red")) + + # look for rounding errors (based on code by Tim Hatch) + def test_rounding_errors(self): + + for color in list(ImageColor.colormap.keys()): + expected = Image.new( + "RGB", (1, 1), color).convert("L").getpixel((0, 0)) + actual = Image.new("L", (1, 1), color).getpixel((0, 0)) + self.assertEqual(expected, actual) + + self.assertEqual((0, 0, 0), ImageColor.getcolor("black", "RGB")) + self.assertEqual((255, 255, 255), ImageColor.getcolor("white", "RGB")) + self.assertEqual( + (0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) + Image.new("RGB", (1, 1), "white") + + self.assertEqual((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) + self.assertEqual( + (255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) + self.assertEqual( + (0, 255, 115, 33), + ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) + Image.new("RGBA", (1, 1), "white") + + self.assertEqual(0, ImageColor.getcolor("black", "L")) + self.assertEqual(255, ImageColor.getcolor("white", "L")) + self.assertEqual( + 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) + Image.new("L", (1, 1), "white") + + self.assertEqual(0, ImageColor.getcolor("black", "1")) + self.assertEqual(255, ImageColor.getcolor("white", "1")) + # The following test is wrong, but is current behavior + # The correct result should be 255 due to the mode 1 + self.assertEqual( + 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) + # Correct behavior + # self.assertEqual( + # 255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) + Image.new("1", (1, 1), "white") + + self.assertEqual((0, 255), ImageColor.getcolor("black", "LA")) + self.assertEqual((255, 255), ImageColor.getcolor("white", "LA")) + self.assertEqual( + (162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) + Image.new("LA", (1, 1), "white") + + +if __name__ == '__main__': + unittest.main() + +# End of file From e01e3e90d592e6c0865be77b66e4b32f7407f49a Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 5 Jun 2014 18:15:12 +0300 Subject: [PATCH 086/488] Convert more tests --- Tests/test_file_xpm.py | 14 ---- Tests/test_format_lab.py | 41 ----------- Tests/test_image_getbands.py | 15 ---- Tests/test_image_putalpha.py | 43 ------------ Tests/test_image_transform.py | 116 ------------------------------- test/test_file_xpm.py | 23 +++++++ test/test_format_lab.py | 48 +++++++++++++ test/test_image_getbands.py | 26 +++++++ test/test_image_putalpha.py | 52 ++++++++++++++ test/test_image_transform.py | 125 ++++++++++++++++++++++++++++++++++ 10 files changed, 274 insertions(+), 229 deletions(-) delete mode 100644 Tests/test_file_xpm.py delete mode 100644 Tests/test_format_lab.py delete mode 100644 Tests/test_image_getbands.py delete mode 100644 Tests/test_image_putalpha.py delete mode 100644 Tests/test_image_transform.py create mode 100644 test/test_file_xpm.py create mode 100644 test/test_format_lab.py create mode 100644 test/test_image_getbands.py create mode 100644 test/test_image_putalpha.py create mode 100644 test/test_image_transform.py diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py deleted file mode 100644 index 44135d028..000000000 --- a/Tests/test_file_xpm.py +++ /dev/null @@ -1,14 +0,0 @@ -from tester import * - -from PIL import Image - -# sample ppm stream -file = "Images/lena.xpm" -data = open(file, "rb").read() - -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "P") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "XPM") diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py deleted file mode 100644 index 371b06a0b..000000000 --- a/Tests/test_format_lab.py +++ /dev/null @@ -1,41 +0,0 @@ -from tester import * - -from PIL import Image - -def test_white(): - i = Image.open('Tests/images/lab.tif') - - bits = i.load() - - assert_equal(i.mode, 'LAB') - - assert_equal(i.getbands(), ('L','A', 'B')) - - k = i.getpixel((0,0)) - assert_equal(k, (255,128,128)) - - L = i.getdata(0) - a = i.getdata(1) - b = i.getdata(2) - - assert_equal(list(L), [255]*100) - assert_equal(list(a), [128]*100) - assert_equal(list(b), [128]*100) - - -def test_green(): - # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS - # == RGB: 0, 152, 117 - i = Image.open('Tests/images/lab-green.tif') - - k = i.getpixel((0,0)) - assert_equal(k, (128,28,128)) - - -def test_red(): - # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS - # == RGB: 255, 0, 124 - i = Image.open('Tests/images/lab-red.tif') - - k = i.getpixel((0,0)) - assert_equal(k, (128,228,128)) diff --git a/Tests/test_image_getbands.py b/Tests/test_image_getbands.py deleted file mode 100644 index e7f1ec5a9..000000000 --- a/Tests/test_image_getbands.py +++ /dev/null @@ -1,15 +0,0 @@ -from tester import * - -from PIL import Image - -def test_getbands(): - - assert_equal(Image.new("1", (1, 1)).getbands(), ("1",)) - assert_equal(Image.new("L", (1, 1)).getbands(), ("L",)) - assert_equal(Image.new("I", (1, 1)).getbands(), ("I",)) - assert_equal(Image.new("F", (1, 1)).getbands(), ("F",)) - assert_equal(Image.new("P", (1, 1)).getbands(), ("P",)) - assert_equal(Image.new("RGB", (1, 1)).getbands(), ("R", "G", "B")) - assert_equal(Image.new("RGBA", (1, 1)).getbands(), ("R", "G", "B", "A")) - assert_equal(Image.new("CMYK", (1, 1)).getbands(), ("C", "M", "Y", "K")) - assert_equal(Image.new("YCbCr", (1, 1)).getbands(), ("Y", "Cb", "Cr")) diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py deleted file mode 100644 index b23f69834..000000000 --- a/Tests/test_image_putalpha.py +++ /dev/null @@ -1,43 +0,0 @@ -from tester import * - -from PIL import Image - -def test_interface(): - - im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 0)) - - im = Image.new("RGBA", (1, 1), (1, 2, 3)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 255)) - - im.putalpha(Image.new("L", im.size, 4)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) - - im.putalpha(5) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 5)) - -def test_promote(): - - im = Image.new("L", (1, 1), 1) - assert_equal(im.getpixel((0, 0)), 1) - - im.putalpha(2) - assert_equal(im.mode, 'LA') - assert_equal(im.getpixel((0, 0)), (1, 2)) - - im = Image.new("RGB", (1, 1), (1, 2, 3)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3)) - - im.putalpha(4) - assert_equal(im.mode, 'RGBA') - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) - -def test_readonly(): - - im = Image.new("RGB", (1, 1), (1, 2, 3)) - im.readonly = 1 - - im.putalpha(4) - assert_false(im.readonly) - assert_equal(im.mode, 'RGBA') - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py deleted file mode 100644 index dd9b6fe5c..000000000 --- a/Tests/test_image_transform.py +++ /dev/null @@ -1,116 +0,0 @@ -from tester import * - -from PIL import Image - -def test_extent(): - im = lena('RGB') - (w,h) = im.size - transformed = im.transform(im.size, Image.EXTENT, - (0,0, - w//2,h//2), # ul -> lr - Image.BILINEAR) - - - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - - assert_image_similar(transformed, scaled, 10) # undone -- precision? - -def test_quad(): - # one simple quad transform, equivalent to scale & crop upper left quad - im = lena('RGB') - (w,h) = im.size - transformed = im.transform(im.size, Image.QUAD, - (0,0,0,h//2, - w//2,h//2,w//2,0), # ul -> ccw around quad - Image.BILINEAR) - - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - - assert_image_equal(transformed, scaled) - -def test_mesh(): - # this should be a checkerboard of halfsized lenas in ul, lr - im = lena('RGBA') - (w,h) = im.size - transformed = im.transform(im.size, Image.MESH, - [((0,0,w//2,h//2), # box - (0,0,0,h, - w,h,w,0)), # ul -> ccw around quad - ((w//2,h//2,w,h), # box - (0,0,0,h, - w,h,w,0))], # ul -> ccw around quad - Image.BILINEAR) - - #transformed.save('transformed.png') - - scaled = im.resize((w//2, h//2), Image.BILINEAR) - - checker = Image.new('RGBA', im.size) - checker.paste(scaled, (0,0)) - checker.paste(scaled, (w//2,h//2)) - - assert_image_equal(transformed, checker) - - # now, check to see that the extra area is (0,0,0,0) - blank = Image.new('RGBA', (w//2,h//2), (0,0,0,0)) - - assert_image_equal(blank, transformed.crop((w//2,0,w,h//2))) - assert_image_equal(blank, transformed.crop((0,h//2,w//2,h))) - -def _test_alpha_premult(op): - # create image with half white, half black, with the black half transparent. - # do op, - # there should be no darkness in the white section. - im = Image.new('RGBA', (10,10), (0,0,0,0)); - im2 = Image.new('RGBA', (5,10), (255,255,255,255)); - im.paste(im2, (0,0)) - - im = op(im, (40,10)) - im_background = Image.new('RGB', (40,10), (255,255,255)) - im_background.paste(im, (0,0), im) - - hist = im_background.histogram() - assert_equal(40*10, hist[-1]) - - -def test_alpha_premult_resize(): - - def op (im, sz): - return im.resize(sz, Image.LINEAR) - - _test_alpha_premult(op) - -def test_alpha_premult_transform(): - - def op(im, sz): - (w,h) = im.size - return im.transform(sz, Image.EXTENT, - (0,0, - w,h), - Image.BILINEAR) - - _test_alpha_premult(op) - - -def test_blank_fill(): - # attempting to hit - # https://github.com/python-pillow/Pillow/issues/254 reported - # - # issue is that transforms with transparent overflow area - # contained junk from previous images, especially on systems with - # constrained memory. So, attempt to fill up memory with a - # pattern, free it, and then run the mesh test again. Using a 1Mp - # image with 4 bands, for 4 megs of data allocated, x 64. OMM (64 - # bit 12.04 VM with 512 megs available, this fails with Pillow < - # a0eaf06cc5f62a6fb6de556989ac1014ff3348ea - # - # Running by default, but I'd totally understand not doing it in - # the future - - foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) - for a in range(1,65)] - - # Yeah. Watch some JIT optimize this out. - foo = None - - test_mesh() diff --git a/test/test_file_xpm.py b/test/test_file_xpm.py new file mode 100644 index 000000000..ecbb4137a --- /dev/null +++ b/test/test_file_xpm.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + +# sample ppm stream +file = "Images/lena.xpm" +data = open(file, "rb").read() + + +class TestFileXpm(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "XPM") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_format_lab.py b/test/test_format_lab.py new file mode 100644 index 000000000..53468db5f --- /dev/null +++ b/test/test_format_lab.py @@ -0,0 +1,48 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestFormatLab(PillowTestCase): + + def test_white(self): + i = Image.open('Tests/images/lab.tif') + + i.load() + + self.assertEqual(i.mode, 'LAB') + + self.assertEqual(i.getbands(), ('L', 'A', 'B')) + + k = i.getpixel((0, 0)) + self.assertEqual(k, (255, 128, 128)) + + L = i.getdata(0) + a = i.getdata(1) + b = i.getdata(2) + + self.assertEqual(list(L), [255]*100) + self.assertEqual(list(a), [128]*100) + self.assertEqual(list(b), [128]*100) + + def test_green(self): + # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS + # == RGB: 0, 152, 117 + i = Image.open('Tests/images/lab-green.tif') + + k = i.getpixel((0, 0)) + self.assertEqual(k, (128, 28, 128)) + + def test_red(self): + # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS + # == RGB: 255, 0, 124 + i = Image.open('Tests/images/lab-red.tif') + + k = i.getpixel((0, 0)) + self.assertEqual(k, (128, 228, 128)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_getbands.py b/test/test_image_getbands.py new file mode 100644 index 000000000..e803abb02 --- /dev/null +++ b/test/test_image_getbands.py @@ -0,0 +1,26 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestImageGetBands(PillowTestCase): + + def test_getbands(self): + self.assertEqual(Image.new("1", (1, 1)).getbands(), ("1",)) + self.assertEqual(Image.new("L", (1, 1)).getbands(), ("L",)) + self.assertEqual(Image.new("I", (1, 1)).getbands(), ("I",)) + self.assertEqual(Image.new("F", (1, 1)).getbands(), ("F",)) + self.assertEqual(Image.new("P", (1, 1)).getbands(), ("P",)) + self.assertEqual(Image.new("RGB", (1, 1)).getbands(), ("R", "G", "B")) + self.assertEqual( + Image.new("RGBA", (1, 1)).getbands(), ("R", "G", "B", "A")) + self.assertEqual( + Image.new("CMYK", (1, 1)).getbands(), ("C", "M", "Y", "K")) + self.assertEqual( + Image.new("YCbCr", (1, 1)).getbands(), ("Y", "Cb", "Cr")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_putalpha.py b/test/test_image_putalpha.py new file mode 100644 index 000000000..85c7ac262 --- /dev/null +++ b/test/test_image_putalpha.py @@ -0,0 +1,52 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestImagePutAlpha(PillowTestCase): + + def test_interface(self): + + im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 0)) + + im = Image.new("RGBA", (1, 1), (1, 2, 3)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 255)) + + im.putalpha(Image.new("L", im.size, 4)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) + + im.putalpha(5) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 5)) + + def test_promote(self): + + im = Image.new("L", (1, 1), 1) + self.assertEqual(im.getpixel((0, 0)), 1) + + im.putalpha(2) + self.assertEqual(im.mode, 'LA') + self.assertEqual(im.getpixel((0, 0)), (1, 2)) + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3)) + + im.putalpha(4) + self.assertEqual(im.mode, 'RGBA') + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) + + def test_readonly(self): + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + im.readonly = 1 + + im.putalpha(4) + self.assertFalse(im.readonly) + self.assertEqual(im.mode, 'RGBA') + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_transform.py b/test/test_image_transform.py new file mode 100644 index 000000000..6ab186c12 --- /dev/null +++ b/test/test_image_transform.py @@ -0,0 +1,125 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImageTransform(PillowTestCase): + + def test_extent(self): + im = lena('RGB') + (w, h) = im.size + transformed = im.transform(im.size, Image.EXTENT, + (0, 0, + w//2, h//2), # ul -> lr + Image.BILINEAR) + + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) + + # undone -- precision? + self.assert_image_similar(transformed, scaled, 10) + + def test_quad(self): + # one simple quad transform, equivalent to scale & crop upper left quad + im = lena('RGB') + (w, h) = im.size + transformed = im.transform(im.size, Image.QUAD, + (0, 0, 0, h//2, + # ul -> ccw around quad: + w//2, h//2, w//2, 0), + Image.BILINEAR) + + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) + + self.assert_image_equal(transformed, scaled) + + def test_mesh(self): + # this should be a checkerboard of halfsized lenas in ul, lr + im = lena('RGBA') + (w, h) = im.size + transformed = im.transform(im.size, Image.MESH, + [((0, 0, w//2, h//2), # box + (0, 0, 0, h, + w, h, w, 0)), # ul -> ccw around quad + ((w//2, h//2, w, h), # box + (0, 0, 0, h, + w, h, w, 0))], # ul -> ccw around quad + Image.BILINEAR) + + # transformed.save('transformed.png') + + scaled = im.resize((w//2, h//2), Image.BILINEAR) + + checker = Image.new('RGBA', im.size) + checker.paste(scaled, (0, 0)) + checker.paste(scaled, (w//2, h//2)) + + self.assert_image_equal(transformed, checker) + + # now, check to see that the extra area is (0, 0, 0, 0) + blank = Image.new('RGBA', (w//2, h//2), (0, 0, 0, 0)) + + self.assert_image_equal(blank, transformed.crop((w//2, 0, w, h//2))) + self.assert_image_equal(blank, transformed.crop((0, h//2, w//2, h))) + + def _test_alpha_premult(self, op): + # create image with half white, half black, + # with the black half transparent. + # do op, + # there should be no darkness in the white section. + im = Image.new('RGBA', (10, 10), (0, 0, 0, 0)) + im2 = Image.new('RGBA', (5, 10), (255, 255, 255, 255)) + im.paste(im2, (0, 0)) + + im = op(im, (40, 10)) + im_background = Image.new('RGB', (40, 10), (255, 255, 255)) + im_background.paste(im, (0, 0), im) + + hist = im_background.histogram() + self.assertEqual(40*10, hist[-1]) + + def test_alpha_premult_resize(self): + + def op(im, sz): + return im.resize(sz, Image.LINEAR) + + self._test_alpha_premult(op) + + def test_alpha_premult_transform(self): + + def op(im, sz): + (w, h) = im.size + return im.transform(sz, Image.EXTENT, + (0, 0, + w, h), + Image.BILINEAR) + + self._test_alpha_premult(op) + + def test_blank_fill(self): + # attempting to hit + # https://github.com/python-pillow/Pillow/issues/254 reported + # + # issue is that transforms with transparent overflow area + # contained junk from previous images, especially on systems with + # constrained memory. So, attempt to fill up memory with a + # pattern, free it, and then run the mesh test again. Using a 1Mp + # image with 4 bands, for 4 megs of data allocated, x 64. OMM (64 + # bit 12.04 VM with 512 megs available, this fails with Pillow < + # a0eaf06cc5f62a6fb6de556989ac1014ff3348ea + # + # Running by default, but I'd totally understand not doing it in + # the future + + foo = [Image.new('RGBA', (1024, 1024), (a, a, a, a)) + for a in range(1, 65)] + + # Yeah. Watch some JIT optimize this out. + foo = None + + self.test_mesh() + + +if __name__ == '__main__': + unittest.main() + +# End of file From e87ae09328cde2da7193f696e10ae4480ff3c7f3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Jun 2014 21:31:26 +0300 Subject: [PATCH 087/488] Delete test test [CI skip] --- test/mini.py | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 test/mini.py diff --git a/test/mini.py b/test/mini.py deleted file mode 100644 index c9f1665ab..000000000 --- a/test/mini.py +++ /dev/null @@ -1,5 +0,0 @@ -from test_image_putpixel import TestImagePutPixel as put -from test_image_getpixel import TestImageGetPixel as get - -dir(get) -get.test_basic() \ No newline at end of file From 0eb92eccd7d4528093b21f6a9b3f3396da16fede Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Jun 2014 22:10:12 +0300 Subject: [PATCH 088/488] Skip test_floodfill_border() on PyPy Otherwise it sometimes, but not always, causes an error: RPython traceback: File "rpython_jit_metainterp_compile.c", line 20472, in send_loop_to_backend File "rpython_jit_backend_x86_assembler.c", line 1818, in Assembler386_assemble_loop File "rpython_jit_backend_x86_regalloc.c", line 293, in RegAlloc_prepare_loop File "rpython_jit_backend_x86_regalloc.c", line 909, in RegAlloc__prepare File "rpython_jit_backend_llsupport_regalloc.c", line 4706, in compute_vars_longevity Fatal RPython error: AssertionError /home/travis/build.sh: line 236: 7300 Aborted --- test/test_imagedraw.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py index 44403e50c..98876296f 100644 --- a/test/test_imagedraw.py +++ b/test/test_imagedraw.py @@ -4,6 +4,8 @@ from PIL import Image from PIL import ImageColor from PIL import ImageDraw +import sys + # Image size w, h = 100, 100 @@ -225,7 +227,11 @@ class TestImageDraw(PillowTestCase): self.assert_image_equal( im, Image.open("Tests/images/imagedraw_floodfill.png")) + @unittest.skipIf(hasattr(sys, 'pypy_version_info'), + "Causes fatal RPython error on PyPy") def test_floodfill_border(self): + # floodfill() is experimental + # Arrange im = Image.new("RGB", (w, h)) draw = ImageDraw.Draw(im) From 995f3677559f6387ed43a3b224856de3a732fe14 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 08:02:18 +0300 Subject: [PATCH 089/488] Skip test_floodfill_border() on PyPy to avoid fatal error --- Tests/test_imagedraw.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index c47638a05..7b682020e 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -246,6 +246,11 @@ def test_floodfill(): def test_floodfill_border(): + # floodfill() is experimental + if hasattr(sys, 'pypy_version_info'): + # Causes fatal RPython error on PyPy + skip() + # Arrange im = Image.new("RGB", (w, h)) draw = ImageDraw.Draw(im) From 4ab5b6c583f5c21ae065cbacb66bc6b25f92005b Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 10:48:28 +0300 Subject: [PATCH 090/488] Comment out lena() caching --- test/helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/helper.py b/test/helper.py index e6684b845..455ff9673 100644 --- a/test/helper.py +++ b/test/helper.py @@ -169,9 +169,9 @@ def tostring(im, format, **options): return out.getvalue() -def lena(mode="RGB", cache={}): +def lena(mode="RGB"): # , cache={}): from PIL import Image - im = cache.get(mode) + # im = cache.get(mode) if im is None: if mode == "RGB": im = Image.open("Images/lena.ppm") @@ -181,7 +181,7 @@ def lena(mode="RGB", cache={}): im = lena("I").convert(mode) else: im = lena("RGB").convert(mode) - cache[mode] = im + # cache[mode] = im return im From 40ada60ceceedb67a1e91121cb89f49862f9bf9f Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 10:55:04 +0300 Subject: [PATCH 091/488] Comment out lena() caching --- test/helper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helper.py b/test/helper.py index 455ff9673..cfe7b86c7 100644 --- a/test/helper.py +++ b/test/helper.py @@ -172,6 +172,7 @@ def tostring(im, format, **options): def lena(mode="RGB"): # , cache={}): from PIL import Image # im = cache.get(mode) + im = None if im is None: if mode == "RGB": im = Image.open("Images/lena.ppm") From e2e361141c1d4f90542a198e427de2450321b407 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 14:04:26 +0300 Subject: [PATCH 092/488] Return duplicate in lena() --- .travis.yml | 12 ++++++------ test/helper.py | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 48206a64d..ef5094a68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,14 +29,14 @@ script: - python setup.py build_ext --inplace # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then nosetests test/; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests test/; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python Tests/run.py; fi # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* -m nose test/; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose test/; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time python Tests/run.py --coverage; fi after_success: diff --git a/test/helper.py b/test/helper.py index cfe7b86c7..9fe66a721 100644 --- a/test/helper.py +++ b/test/helper.py @@ -169,10 +169,9 @@ def tostring(im, format, **options): return out.getvalue() -def lena(mode="RGB"): # , cache={}): +def lena(mode="RGB"), cache={}): from PIL import Image - # im = cache.get(mode) - im = None + im = cache.get(mode) if im is None: if mode == "RGB": im = Image.open("Images/lena.ppm") @@ -182,8 +181,10 @@ def lena(mode="RGB"): # , cache={}): im = lena("I").convert(mode) else: im = lena("RGB").convert(mode) - # cache[mode] = im - return im + cache[mode] = im + import copy + duplicate = copy.copy(im) + return duplicate # def assert_image_completely_equal(a, b, msg=None): From c45f20119c2ba40d4802f984e8d54afacd160a2b Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 14:53:33 +0300 Subject: [PATCH 093/488] Fix syntax error --- test/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helper.py b/test/helper.py index 9fe66a721..f97faf4d1 100644 --- a/test/helper.py +++ b/test/helper.py @@ -169,7 +169,7 @@ def tostring(im, format, **options): return out.getvalue() -def lena(mode="RGB"), cache={}): +def lena(mode="RGB", cache={}): from PIL import Image im = cache.get(mode) if im is None: From 3ff2ea4883b32303c6baacbc5df06d3fc76c4a1a Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 15:19:28 +0300 Subject: [PATCH 094/488] Disable lena() caching for now, convert more tests --- Tests/test_file_icns.py | 66 -------------------------------- Tests/test_image_resize.py | 12 ------ Tests/test_imagefileio.py | 24 ------------ Tests/test_imagetransform.py | 18 --------- Tests/test_locale.py | 31 --------------- test/helper.py | 9 ++--- test/test_file_icns.py | 74 ++++++++++++++++++++++++++++++++++++ test/test_image_resize.py | 19 +++++++++ test/test_imagefileio.py | 31 +++++++++++++++ test/test_imagetransform.py | 27 +++++++++++++ test/test_locale.py | 39 +++++++++++++++++++ 11 files changed, 194 insertions(+), 156 deletions(-) delete mode 100644 Tests/test_file_icns.py delete mode 100644 Tests/test_image_resize.py delete mode 100644 Tests/test_imagefileio.py delete mode 100644 Tests/test_imagetransform.py delete mode 100644 Tests/test_locale.py create mode 100644 test/test_file_icns.py create mode 100644 test/test_image_resize.py create mode 100644 test/test_imagefileio.py create mode 100644 test/test_imagetransform.py create mode 100644 test/test_locale.py diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py deleted file mode 100644 index 3e31f8879..000000000 --- a/Tests/test_file_icns.py +++ /dev/null @@ -1,66 +0,0 @@ -from tester import * - -from PIL import Image - -# sample icon file -file = "Images/pillow.icns" -data = open(file, "rb").read() - -enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') - -def test_sanity(): - # Loading this icon by default should result in the largest size - # (512x512@2x) being loaded - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGBA") - assert_equal(im.size, (1024, 1024)) - assert_equal(im.format, "ICNS") - -def test_sizes(): - # Check that we can load all of the sizes, and that the final pixel - # dimensions are as expected - im = Image.open(file) - for w,h,r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open(file) - im2.size = (w, h, r) - im2.load() - assert_equal(im2.mode, 'RGBA') - assert_equal(im2.size, (wr, hr)) - -def test_older_icon(): - # This icon was made with Icon Composer rather than iconutil; it still - # uses PNG rather than JP2, however (since it was made on 10.9). - im = Image.open('Tests/images/pillow2.icns') - for w,h,r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open('Tests/images/pillow2.icns') - im2.size = (w, h, r) - im2.load() - assert_equal(im2.mode, 'RGBA') - assert_equal(im2.size, (wr, hr)) - -def test_jp2_icon(): - # This icon was made by using Uli Kusterer's oldiconutil to replace - # the PNG images with JPEG 2000 ones. The advantage of doing this is - # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial - # software therefore does just this. - - # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) - - if not enable_jpeg2k: - return - - im = Image.open('Tests/images/pillow3.icns') - for w,h,r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open('Tests/images/pillow3.icns') - im2.size = (w, h, r) - im2.load() - assert_equal(im2.mode, 'RGBA') - assert_equal(im2.size, (wr, hr)) - diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py deleted file mode 100644 index 4e228a396..000000000 --- a/Tests/test_image_resize.py +++ /dev/null @@ -1,12 +0,0 @@ -from tester import * - -from PIL import Image - -def test_resize(): - def resize(mode, size): - out = lena(mode).resize(size) - assert_equal(out.mode, mode) - assert_equal(out.size, size) - for mode in "1", "P", "L", "RGB", "I", "F": - yield_test(resize, mode, (100, 100)) - yield_test(resize, mode, (200, 200)) diff --git a/Tests/test_imagefileio.py b/Tests/test_imagefileio.py deleted file mode 100644 index c63f07bb0..000000000 --- a/Tests/test_imagefileio.py +++ /dev/null @@ -1,24 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageFileIO - -def test_fileio(): - - class DumbFile: - def __init__(self, data): - self.data = data - def read(self, bytes=None): - assert_equal(bytes, None) - return self.data - def close(self): - pass - - im1 = lena() - - io = ImageFileIO.ImageFileIO(DumbFile(tostring(im1, "PPM"))) - - im2 = Image.open(io) - assert_image_equal(im1, im2) - - diff --git a/Tests/test_imagetransform.py b/Tests/test_imagetransform.py deleted file mode 100644 index 884e6bb1c..000000000 --- a/Tests/test_imagetransform.py +++ /dev/null @@ -1,18 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageTransform - -im = Image.new("L", (100, 100)) - -seq = tuple(range(10)) - -def test_sanity(): - transform = ImageTransform.AffineTransform(seq[:6]) - assert_no_exception(lambda: im.transform((100, 100), transform)) - transform = ImageTransform.ExtentTransform(seq[:4]) - assert_no_exception(lambda: im.transform((100, 100), transform)) - transform = ImageTransform.QuadTransform(seq[:8]) - assert_no_exception(lambda: im.transform((100, 100), transform)) - transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) - assert_no_exception(lambda: im.transform((100, 100), transform)) diff --git a/Tests/test_locale.py b/Tests/test_locale.py deleted file mode 100644 index 6b2b95201..000000000 --- a/Tests/test_locale.py +++ /dev/null @@ -1,31 +0,0 @@ -from tester import * -from PIL import Image - -import locale - -# ref https://github.com/python-pillow/Pillow/issues/272 -## on windows, in polish locale: - -## import locale -## print locale.setlocale(locale.LC_ALL, 'polish') -## import string -## print len(string.whitespace) -## print ord(string.whitespace[6]) - -## Polish_Poland.1250 -## 7 -## 160 - -# one of string.whitespace is not freely convertable into ascii. - -path = "Images/lena.jpg" - -def test_sanity(): - assert_no_exception(lambda: Image.open(path)) - try: - locale.setlocale(locale.LC_ALL, "polish") - except: - skip('polish locale not available') - import string - assert_no_exception(lambda: Image.open(path)) - diff --git a/test/helper.py b/test/helper.py index f97faf4d1..ebe651120 100644 --- a/test/helper.py +++ b/test/helper.py @@ -171,7 +171,8 @@ def tostring(im, format, **options): def lena(mode="RGB", cache={}): from PIL import Image - im = cache.get(mode) + im = None + # im = cache.get(mode) if im is None: if mode == "RGB": im = Image.open("Images/lena.ppm") @@ -181,10 +182,8 @@ def lena(mode="RGB", cache={}): im = lena("I").convert(mode) else: im = lena("RGB").convert(mode) - cache[mode] = im - import copy - duplicate = copy.copy(im) - return duplicate + # cache[mode] = im + return im # def assert_image_completely_equal(a, b, msg=None): diff --git a/test/test_file_icns.py b/test/test_file_icns.py new file mode 100644 index 000000000..9d838620b --- /dev/null +++ b/test/test_file_icns.py @@ -0,0 +1,74 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + +# sample icon file +file = "Images/pillow.icns" +data = open(file, "rb").read() + +enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') + + +class TestFileIcns(PillowTestCase): + + def test_sanity(self): + # Loading this icon by default should result in the largest size + # (512x512@2x) being loaded + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (1024, 1024)) + self.assertEqual(im.format, "ICNS") + + def test_sizes(self): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + im = Image.open(file) + for w, h, r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open(file) + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.size, (wr, hr)) + + def test_older_icon(self): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + im = Image.open('Tests/images/pillow2.icns') + for w, h, r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow2.icns') + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.size, (wr, hr)) + + def test_jp2_icon(self): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. + + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) + + if not enable_jpeg2k: + return + + im = Image.open('Tests/images/pillow3.icns') + for w, h, r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow3.icns') + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.size, (wr, hr)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_resize.py b/test/test_image_resize.py new file mode 100644 index 000000000..6c9932e45 --- /dev/null +++ b/test/test_image_resize.py @@ -0,0 +1,19 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImageResize(PillowTestCase): + + def test_resize(self): + def resize(mode, size): + out = lena(mode).resize(size) + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, size) + for mode in "1", "P", "L", "RGB", "I", "F": + resize(mode, (100, 100)) + resize(mode, (200, 200)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagefileio.py b/test/test_imagefileio.py new file mode 100644 index 000000000..73f660361 --- /dev/null +++ b/test/test_imagefileio.py @@ -0,0 +1,31 @@ +from helper import unittest, PillowTestCase, lena, tostring + +from PIL import Image +from PIL import ImageFileIO + + +class TestImageFileIo(PillowTestCase): + + def test_fileio(self): + + class DumbFile: + def __init__(self, data): + self.data = data + def read(self, bytes=None): + self.assertEqual(bytes, None) + return self.data + def close(self): + pass + + im1 = lena() + + io = ImageFileIO.ImageFileIO(DumbFile(tostring(im1, "PPM"))) + + im2 = Image.open(io) + self.assert_image_equal(im1, im2) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagetransform.py b/test/test_imagetransform.py new file mode 100644 index 000000000..f5741df32 --- /dev/null +++ b/test/test_imagetransform.py @@ -0,0 +1,27 @@ +from helper import unittest, PillowTestCase + +from PIL import Image +from PIL import ImageTransform + + +class TestImageTransform(PillowTestCase): + + def test_sanity(self): + im = Image.new("L", (100, 100)) + + seq = tuple(range(10)) + + transform = ImageTransform.AffineTransform(seq[:6]) + im.transform((100, 100), transform) + transform = ImageTransform.ExtentTransform(seq[:4]) + im.transform((100, 100), transform) + transform = ImageTransform.QuadTransform(seq[:8]) + im.transform((100, 100), transform) + transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) + im.transform((100, 100), transform) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_locale.py b/test/test_locale.py new file mode 100644 index 000000000..599e46266 --- /dev/null +++ b/test/test_locale.py @@ -0,0 +1,39 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + +import locale + +# ref https://github.com/python-pillow/Pillow/issues/272 +# on windows, in polish locale: + +# import locale +# print locale.setlocale(locale.LC_ALL, 'polish') +# import string +# print len(string.whitespace) +# print ord(string.whitespace[6]) + +# Polish_Poland.1250 +# 7 +# 160 + +# one of string.whitespace is not freely convertable into ascii. + +path = "Images/lena.jpg" + + +class TestLocale(PillowTestCase): + + def test_sanity(self): + Image.open(path) + try: + locale.setlocale(locale.LC_ALL, "polish") + except: + unittest.skip('Polish locale not available') + Image.open(path) + + +if __name__ == '__main__': + unittest.main() + +# End of file From cbb2f9dce941698b448c579034687c7f57145970 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 15:26:48 +0300 Subject: [PATCH 095/488] Fix test/test_imagefileio.py --- Tests/test_image_paste.py | 5 ----- Tests/test_image_save.py | 5 ----- Tests/test_image_seek.py | 5 ----- Tests/test_image_show.py | 5 ----- Tests/test_image_tell.py | 5 ----- Tests/test_image_verify.py | 5 ----- test/test_imagefileio.py | 4 +++- 7 files changed, 3 insertions(+), 31 deletions(-) delete mode 100644 Tests/test_image_paste.py delete mode 100644 Tests/test_image_save.py delete mode 100644 Tests/test_image_seek.py delete mode 100644 Tests/test_image_show.py delete mode 100644 Tests/test_image_tell.py delete mode 100644 Tests/test_image_verify.py diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_paste.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_save.py b/Tests/test_image_save.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_save.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_seek.py b/Tests/test_image_seek.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_seek.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_show.py b/Tests/test_image_show.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_show.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_tell.py b/Tests/test_image_tell.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_tell.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_verify.py b/Tests/test_image_verify.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_verify.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/test/test_imagefileio.py b/test/test_imagefileio.py index 73f660361..32ee0bc5e 100644 --- a/test/test_imagefileio.py +++ b/test/test_imagefileio.py @@ -11,9 +11,11 @@ class TestImageFileIo(PillowTestCase): class DumbFile: def __init__(self, data): self.data = data + def read(self, bytes=None): - self.assertEqual(bytes, None) + assert(bytes is None) return self.data + def close(self): pass From c4d3898006b5d5c5af90312e15ba558e1bcd3a01 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 16:55:00 +0300 Subject: [PATCH 096/488] Convert more tests --- Tests/test_file_ico.py | 14 --- Tests/test_font_bdf.py | 13 --- Tests/test_image_offset.py | 16 --- Tests/test_imagecms.py | 203 ----------------------------------- Tests/test_imagemode.py | 23 ---- Tests/test_imageshow.py | 6 -- Tests/test_imagetk.py | 9 -- Tests/test_imagewin.py | 6 -- test/helper.py | 5 +- test/test_file_ico.py | 23 ++++ test/test_font_bdf.py | 22 ++++ test/test_image_offset.py | 25 +++++ test/test_imagecms.py | 214 +++++++++++++++++++++++++++++++++++++ test/test_imagemode.py | 32 ++++++ test/test_imageshow.py | 18 ++++ test/test_imagetk.py | 17 +++ test/test_imagewin.py | 18 ++++ 17 files changed, 372 insertions(+), 292 deletions(-) delete mode 100644 Tests/test_file_ico.py delete mode 100644 Tests/test_font_bdf.py delete mode 100644 Tests/test_image_offset.py delete mode 100644 Tests/test_imagecms.py delete mode 100644 Tests/test_imagemode.py delete mode 100644 Tests/test_imageshow.py delete mode 100644 Tests/test_imagetk.py delete mode 100644 Tests/test_imagewin.py create mode 100644 test/test_file_ico.py create mode 100644 test/test_font_bdf.py create mode 100644 test/test_image_offset.py create mode 100644 test/test_imagecms.py create mode 100644 test/test_imagemode.py create mode 100644 test/test_imageshow.py create mode 100644 test/test_imagetk.py create mode 100644 test/test_imagewin.py diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py deleted file mode 100644 index e0db34acc..000000000 --- a/Tests/test_file_ico.py +++ /dev/null @@ -1,14 +0,0 @@ -from tester import * - -from PIL import Image - -# sample ppm stream -file = "Images/lena.ico" -data = open(file, "rb").read() - -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGBA") - assert_equal(im.size, (16, 16)) - assert_equal(im.format, "ICO") diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py deleted file mode 100644 index 366bb4468..000000000 --- a/Tests/test_font_bdf.py +++ /dev/null @@ -1,13 +0,0 @@ -from tester import * - -from PIL import Image, FontFile, BdfFontFile - -filename = "Images/courB08.bdf" - -def test_sanity(): - - file = open(filename, "rb") - font = BdfFontFile.BdfFontFile(file) - - assert_true(isinstance(font, FontFile.FontFile)) - assert_equal(len([_f for _f in font.glyph if _f]), 190) diff --git a/Tests/test_image_offset.py b/Tests/test_image_offset.py deleted file mode 100644 index f6356907a..000000000 --- a/Tests/test_image_offset.py +++ /dev/null @@ -1,16 +0,0 @@ -from tester import * - -from PIL import Image - -def test_offset(): - - im1 = lena() - - im2 = assert_warning(DeprecationWarning, lambda: im1.offset(10)) - assert_equal(im1.getpixel((0, 0)), im2.getpixel((10, 10))) - - im2 = assert_warning(DeprecationWarning, lambda: im1.offset(10, 20)) - assert_equal(im1.getpixel((0, 0)), im2.getpixel((10, 20))) - - im2 = assert_warning(DeprecationWarning, lambda: im1.offset(20, 20)) - assert_equal(im1.getpixel((0, 0)), im2.getpixel((20, 20))) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py deleted file mode 100644 index f52101eb1..000000000 --- a/Tests/test_imagecms.py +++ /dev/null @@ -1,203 +0,0 @@ -from tester import * - -from PIL import Image -try: - from PIL import ImageCms - ImageCms.core.profile_open -except ImportError: - skip() - -SRGB = "Tests/icc/sRGB.icm" - - -def test_sanity(): - - # basic smoke test. - # this mostly follows the cms_test outline. - - v = ImageCms.versions() # should return four strings - assert_equal(v[0], '1.0.0 pil') - assert_equal(list(map(type, v)), [str, str, str, str]) - - # internal version number - assert_match(ImageCms.core.littlecms_version, "\d+\.\d+$") - - i = ImageCms.profileToProfile(lena(), SRGB, SRGB) - assert_image(i, "RGB", (128, 128)) - - i = lena() - ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) - assert_image(i, "RGB", (128, 128)) - - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "RGB", (128, 128)) - - i = lena() - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - ImageCms.applyTransform(lena(), t, inPlace=True) - assert_image(i, "RGB", (128, 128)) - - p = ImageCms.createProfile("sRGB") - o = ImageCms.getOpenProfile(SRGB) - t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "RGB", (128, 128)) - - t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") - assert_equal(t.inputMode, "RGB") - assert_equal(t.outputMode, "RGB") - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "RGB", (128, 128)) - - # test PointTransform convenience API - lena().point(t) - - -def test_name(): - # get profile information for file - assert_equal(ImageCms.getProfileName(SRGB).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - -def test_info(): - assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), - ['sRGB IEC61966-2.1', '', - 'Copyright (c) 1998 Hewlett-Packard Company', '']) - - -def test_copyright(): - assert_equal(ImageCms.getProfileCopyright(SRGB).strip(), - 'Copyright (c) 1998 Hewlett-Packard Company') - - -def test_manufacturer(): - assert_equal(ImageCms.getProfileManufacturer(SRGB).strip(), - 'IEC http://www.iec.ch') - - -def test_model(): - assert_equal(ImageCms.getProfileModel(SRGB).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - -def test_description(): - assert_equal(ImageCms.getProfileDescription(SRGB).strip(), - 'sRGB IEC61966-2.1') - - -def test_intent(): - assert_equal(ImageCms.getDefaultIntent(SRGB), 0) - assert_equal(ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) - - -def test_profile_object(): - # same, using profile object - p = ImageCms.createProfile("sRGB") -# assert_equal(ImageCms.getProfileName(p).strip(), -# 'sRGB built-in - (lcms internal)') -# assert_equal(ImageCms.getProfileInfo(p).splitlines(), -# ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) - assert_equal(ImageCms.getDefaultIntent(p), 0) - assert_equal(ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) - - -def test_extensions(): - # extensions - i = Image.open("Tests/images/rgb.jpg") - p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) - assert_equal(ImageCms.getProfileName(p).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - -def test_exceptions(): - # the procedural pyCMS API uses PyCMSError for all sorts of errors - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.getProfileName(None)) - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.isIntentSupported(SRGB, None, None)) - - -def test_display_profile(): - # try fetching the profile for the current display device - assert_no_exception(lambda: ImageCms.get_display_profile()) - - -def test_lab_color_profile(): - ImageCms.createProfile("LAB", 5000) - ImageCms.createProfile("LAB", 6500) - - -def test_simple_lab(): - i = Image.new('RGB', (10, 10), (128, 128, 128)) - - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - - i_lab = ImageCms.applyTransform(i, t) - - assert_equal(i_lab.mode, 'LAB') - - k = i_lab.getpixel((0, 0)) - assert_equal(k, (137, 128, 128)) # not a linear luminance map. so L != 128 - - L = i_lab.getdata(0) - a = i_lab.getdata(1) - b = i_lab.getdata(2) - - assert_equal(list(L), [137] * 100) - assert_equal(list(a), [128] * 100) - assert_equal(list(b), [128] * 100) - - -def test_lab_color(): - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - # Need to add a type mapping for some PIL type to TYPE_Lab_8 in - # findLCMSType, and have that mapping work back to a PIL mode (likely RGB). - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "LAB", (128, 128)) - - # i.save('temp.lab.tif') # visually verified vs PS. - - target = Image.open('Tests/images/lena.Lab.tif') - - assert_image_similar(i, target, 30) - - -def test_lab_srgb(): - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") - - img = Image.open('Tests/images/lena.Lab.tif') - - img_srgb = ImageCms.applyTransform(img, t) - - # img_srgb.save('temp.srgb.tif') # visually verified vs ps. - - assert_image_similar(lena(), img_srgb, 30) - - -def test_lab_roundtrip(): - # check to see if we're at least internally consistent. - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - - t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") - - i = ImageCms.applyTransform(lena(), t) - out = ImageCms.applyTransform(i, t2) - - assert_image_similar(lena(), out, 2) diff --git a/Tests/test_imagemode.py b/Tests/test_imagemode.py deleted file mode 100644 index 54b04435f..000000000 --- a/Tests/test_imagemode.py +++ /dev/null @@ -1,23 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageMode - -ImageMode.getmode("1") -ImageMode.getmode("L") -ImageMode.getmode("P") -ImageMode.getmode("RGB") -ImageMode.getmode("I") -ImageMode.getmode("F") - -m = ImageMode.getmode("1") -assert_equal(m.mode, "1") -assert_equal(m.bands, ("1",)) -assert_equal(m.basemode, "L") -assert_equal(m.basetype, "L") - -m = ImageMode.getmode("RGB") -assert_equal(m.mode, "RGB") -assert_equal(m.bands, ("R", "G", "B")) -assert_equal(m.basemode, "RGB") -assert_equal(m.basetype, "L") diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py deleted file mode 100644 index 99ec005c8..000000000 --- a/Tests/test_imageshow.py +++ /dev/null @@ -1,6 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageShow - -success() diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py deleted file mode 100644 index b30971e8f..000000000 --- a/Tests/test_imagetk.py +++ /dev/null @@ -1,9 +0,0 @@ -from tester import * - -from PIL import Image -try: - from PIL import ImageTk -except (OSError, ImportError) as v: - skip(v) - -success() diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py deleted file mode 100644 index 268a75b6f..000000000 --- a/Tests/test_imagewin.py +++ /dev/null @@ -1,6 +0,0 @@ -from tester import * - -from PIL import Image -from PIL import ImageWin - -success() diff --git a/test/helper.py b/test/helper.py index ebe651120..54739cf7b 100644 --- a/test/helper.py +++ b/test/helper.py @@ -60,18 +60,19 @@ class PillowTestCase(unittest.TestCase): def assert_warning(self, warn_class, func): import warnings + result = None with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") # Hopefully trigger a warning. - func() + result = func() # Verify some things. self.assertEqual(len(w), 1) assert issubclass(w[-1].category, warn_class) self.assertIn("deprecated", str(w[-1].message)) - + return result # # require that deprecation warnings are triggered # import warnings diff --git a/test/test_file_ico.py b/test/test_file_ico.py new file mode 100644 index 000000000..dc289e1d2 --- /dev/null +++ b/test/test_file_ico.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + +# sample ppm stream +file = "Images/lena.ico" +data = open(file, "rb").read() + + +class TestFileIco(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (16, 16)) + self.assertEqual(im.format, "ICO") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_font_bdf.py b/test/test_font_bdf.py new file mode 100644 index 000000000..b141e6149 --- /dev/null +++ b/test/test_font_bdf.py @@ -0,0 +1,22 @@ +from helper import unittest, PillowTestCase + +from PIL import FontFile, BdfFontFile + +filename = "Images/courB08.bdf" + + +class TestImage(PillowTestCase): + + def test_sanity(self): + + file = open(filename, "rb") + font = BdfFontFile.BdfFontFile(file) + + self.assertIsInstance(font, FontFile.FontFile) + self.assertEqual(len([_f for _f in font.glyph if _f]), 190) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_offset.py b/test/test_image_offset.py new file mode 100644 index 000000000..bb9b5a38c --- /dev/null +++ b/test/test_image_offset.py @@ -0,0 +1,25 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImage(PillowTestCase): + + def test_offset(self): + + im1 = lena() + + im2 = self.assert_warning(DeprecationWarning, lambda: im1.offset(10)) + self.assertEqual(im1.getpixel((0, 0)), im2.getpixel((10, 10))) + + im2 = self.assert_warning( + DeprecationWarning, lambda: im1.offset(10, 20)) + self.assertEqual(im1.getpixel((0, 0)), im2.getpixel((10, 20))) + + im2 = self.assert_warning( + DeprecationWarning, lambda: im1.offset(20, 20)) + self.assertEqual(im1.getpixel((0, 0)), im2.getpixel((20, 20))) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagecms.py b/test/test_imagecms.py new file mode 100644 index 000000000..1a31636e8 --- /dev/null +++ b/test/test_imagecms.py @@ -0,0 +1,214 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + +try: + from PIL import ImageCms + ImageCms.core.profile_open +except ImportError as v: + # Skipped via setUp() + pass + + +SRGB = "Tests/icc/sRGB.icm" + + +class TestImage(PillowTestCase): + + def setUp(self): + try: + from PIL import ImageCms + except ImportError as v: + self.skipTest(v) + + def test_sanity(self): + + # basic smoke test. + # this mostly follows the cms_test outline. + + v = ImageCms.versions() # should return four strings + self.assertEqual(v[0], '1.0.0 pil') + self.assertEqual(list(map(type, v)), [str, str, str, str]) + + # internal version number + self.assertRegexpMatches(ImageCms.core.littlecms_version, "\d+\.\d+$") + + i = ImageCms.profileToProfile(lena(), SRGB, SRGB) + self.assert_image(i, "RGB", (128, 128)) + + i = lena() + ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) + self.assert_image(i, "RGB", (128, 128)) + + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "RGB", (128, 128)) + + i = lena() + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + ImageCms.applyTransform(lena(), t, inPlace=True) + self.assert_image(i, "RGB", (128, 128)) + + p = ImageCms.createProfile("sRGB") + o = ImageCms.getOpenProfile(SRGB) + t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "RGB", (128, 128)) + + t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") + self.assertEqual(t.inputMode, "RGB") + self.assertEqual(t.outputMode, "RGB") + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "RGB", (128, 128)) + + # test PointTransform convenience API + lena().point(t) + + def test_name(self): + # get profile information for file + self.assertEqual( + ImageCms.getProfileName(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_info(self): + self.assertEqual( + ImageCms.getProfileInfo(SRGB).splitlines(), [ + 'sRGB IEC61966-2.1', '', + 'Copyright (c) 1998 Hewlett-Packard Company', '']) + + def test_copyright(self): + self.assertEqual( + ImageCms.getProfileCopyright(SRGB).strip(), + 'Copyright (c) 1998 Hewlett-Packard Company') + + def test_manufacturer(self): + self.assertEqual( + ImageCms.getProfileManufacturer(SRGB).strip(), + 'IEC http://www.iec.ch') + + def test_model(self): + self.assertEqual( + ImageCms.getProfileModel(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_description(self): + self.assertEqual( + ImageCms.getProfileDescription(SRGB).strip(), + 'sRGB IEC61966-2.1') + + def test_intent(self): + self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0) + self.assertEqual(ImageCms.isIntentSupported( + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + + def test_profile_object(self): + # same, using profile object + p = ImageCms.createProfile("sRGB") + # self.assertEqual(ImageCms.getProfileName(p).strip(), + # 'sRGB built-in - (lcms internal)') + # self.assertEqual(ImageCms.getProfileInfo(p).splitlines(), + # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) + self.assertEqual(ImageCms.getDefaultIntent(p), 0) + self.assertEqual(ImageCms.isIntentSupported( + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + + def test_extensions(self): + # extensions + from io import BytesIO + i = Image.open("Tests/images/rgb.jpg") + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + self.assertEqual( + ImageCms.getProfileName(p).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_exceptions(self): + # the procedural pyCMS API uses PyCMSError for all sorts of errors + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.getProfileName(None)) + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.isIntentSupported(SRGB, None, None)) + + def test_display_profile(self): + # try fetching the profile for the current display device + ImageCms.get_display_profile() + + def test_lab_color_profile(self): + ImageCms.createProfile("LAB", 5000) + ImageCms.createProfile("LAB", 6500) + + def test_simple_lab(self): + i = Image.new('RGB', (10, 10), (128, 128, 128)) + + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + + i_lab = ImageCms.applyTransform(i, t) + + self.assertEqual(i_lab.mode, 'LAB') + + k = i_lab.getpixel((0, 0)) + # not a linear luminance map. so L != 128: + self.assertEqual(k, (137, 128, 128)) + + L = i_lab.getdata(0) + a = i_lab.getdata(1) + b = i_lab.getdata(2) + + self.assertEqual(list(L), [137] * 100) + self.assertEqual(list(a), [128] * 100) + self.assertEqual(list(b), [128] * 100) + + def test_lab_color(self): + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + # Need to add a type mapping for some PIL type to TYPE_Lab_8 in + # findLCMSType, and have that mapping work back to a PIL mode + # (likely RGB). + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "LAB", (128, 128)) + + # i.save('temp.lab.tif') # visually verified vs PS. + + target = Image.open('Tests/images/lena.Lab.tif') + + self.assert_image_similar(i, target, 30) + + def test_lab_srgb(self): + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") + + img = Image.open('Tests/images/lena.Lab.tif') + + img_srgb = ImageCms.applyTransform(img, t) + + # img_srgb.save('temp.srgb.tif') # visually verified vs ps. + + self.assert_image_similar(lena(), img_srgb, 30) + + def test_lab_roundtrip(self): + # check to see if we're at least internally consistent. + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + + t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") + + i = ImageCms.applyTransform(lena(), t) + out = ImageCms.applyTransform(i, t2) + + self.assert_image_similar(lena(), out, 2) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagemode.py b/test/test_imagemode.py new file mode 100644 index 000000000..7fb596b46 --- /dev/null +++ b/test/test_imagemode.py @@ -0,0 +1,32 @@ +from helper import unittest, PillowTestCase + +from PIL import ImageMode + + +class TestImage(PillowTestCase): + + def test_sanity(self): + ImageMode.getmode("1") + ImageMode.getmode("L") + ImageMode.getmode("P") + ImageMode.getmode("RGB") + ImageMode.getmode("I") + ImageMode.getmode("F") + + m = ImageMode.getmode("1") + self.assertEqual(m.mode, "1") + self.assertEqual(m.bands, ("1",)) + self.assertEqual(m.basemode, "L") + self.assertEqual(m.basetype, "L") + + m = ImageMode.getmode("RGB") + self.assertEqual(m.mode, "RGB") + self.assertEqual(m.bands, ("R", "G", "B")) + self.assertEqual(m.basemode, "RGB") + self.assertEqual(m.basetype, "L") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imageshow.py b/test/test_imageshow.py new file mode 100644 index 000000000..12594c98f --- /dev/null +++ b/test/test_imageshow.py @@ -0,0 +1,18 @@ +from helper import unittest, PillowTestCase + +from PIL import Image +from PIL import ImageShow + + +class TestImage(PillowTestCase): + + def test_sanity(self): + dir(Image) + dir(ImageShow) + pass + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagetk.py b/test/test_imagetk.py new file mode 100644 index 000000000..87a07e288 --- /dev/null +++ b/test/test_imagetk.py @@ -0,0 +1,17 @@ +from helper import unittest, PillowTestCase + + +class TestImageTk(PillowTestCase): + + def test_import(self): + try: + from PIL import ImageTk + dir(ImageTk) + except (OSError, ImportError) as v: + self.skipTest(v) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagewin.py b/test/test_imagewin.py new file mode 100644 index 000000000..f7a6bc0dc --- /dev/null +++ b/test/test_imagewin.py @@ -0,0 +1,18 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image +from PIL import ImageWin + + +class TestImage(PillowTestCase): + + def test_sanity(self): + dir(Image) + dir(ImageShow) + pass + + +if __name__ == '__main__': + unittest.main() + +# End of file From 3c7c89e642518de369c7c6a30f21faebbddf2824 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 17:05:54 +0300 Subject: [PATCH 097/488] Convert more tests, fix test_imagewin.py --- Tests/test_image_getpalette.py | 19 ----------------- Tests/test_image_mode.py | 27 ------------------------ Tests/test_image_quantize.py | 27 ------------------------ {Tests => test}/test_file_xbm.py | 22 +++++++++++++------ test/test_image_getpalette.py | 26 +++++++++++++++++++++++ test/test_image_mode.py | 36 ++++++++++++++++++++++++++++++++ test/test_image_quantize.py | 35 +++++++++++++++++++++++++++++++ test/test_imagewin.py | 2 +- 8 files changed, 114 insertions(+), 80 deletions(-) delete mode 100644 Tests/test_image_getpalette.py delete mode 100644 Tests/test_image_mode.py delete mode 100644 Tests/test_image_quantize.py rename {Tests => test}/test_file_xbm.py (72%) create mode 100644 test/test_image_getpalette.py create mode 100644 test/test_image_mode.py create mode 100644 test/test_image_quantize.py diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py deleted file mode 100644 index 5dc923b9f..000000000 --- a/Tests/test_image_getpalette.py +++ /dev/null @@ -1,19 +0,0 @@ -from tester import * - -from PIL import Image - -def test_palette(): - def palette(mode): - p = lena(mode).getpalette() - if p: - return p[:10] - return None - assert_equal(palette("1"), None) - assert_equal(palette("L"), None) - assert_equal(palette("I"), None) - assert_equal(palette("F"), None) - assert_equal(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - assert_equal(palette("RGB"), None) - assert_equal(palette("RGBA"), None) - assert_equal(palette("CMYK"), None) - assert_equal(palette("YCbCr"), None) diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py deleted file mode 100644 index cd5bd47f5..000000000 --- a/Tests/test_image_mode.py +++ /dev/null @@ -1,27 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - - im = lena() - assert_no_exception(lambda: im.mode) - -def test_properties(): - def check(mode, *result): - signature = ( - Image.getmodebase(mode), Image.getmodetype(mode), - Image.getmodebands(mode), Image.getmodebandnames(mode), - ) - assert_equal(signature, result) - check("1", "L", "L", 1, ("1",)) - check("L", "L", "L", 1, ("L",)) - check("P", "RGB", "L", 1, ("P",)) - check("I", "L", "I", 1, ("I",)) - check("F", "L", "F", 1, ("F",)) - check("RGB", "RGB", "L", 3, ("R", "G", "B")) - check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")) - check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) - check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) - check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")) - check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py deleted file mode 100644 index dbf68a25e..000000000 --- a/Tests/test_image_quantize.py +++ /dev/null @@ -1,27 +0,0 @@ -from tester import * - -from PIL import Image - -def test_sanity(): - - im = lena() - - im = im.quantize() - assert_image(im, "P", im.size) - - im = lena() - im = im.quantize(palette=lena("P")) - assert_image(im, "P", im.size) - -def test_octree_quantize(): - im = lena() - - im = im.quantize(100, Image.FASTOCTREE) - assert_image(im, "P", im.size) - - assert len(im.getcolors()) == 100 - -def test_rgba_quantize(): - im = lena('RGBA') - assert_no_exception(lambda: im.quantize()) - assert_exception(Exception, lambda: im.quantize(method=0)) diff --git a/Tests/test_file_xbm.py b/test/test_file_xbm.py similarity index 72% rename from Tests/test_file_xbm.py rename to test/test_file_xbm.py index f27a3a349..02aec70b1 100644 --- a/Tests/test_file_xbm.py +++ b/test/test_file_xbm.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase from PIL import Image @@ -25,10 +25,20 @@ static char basic_bits[] = { }; """ -def test_pil151(): - im = Image.open(BytesIO(PIL151)) +class TestFileXbm(PillowTestCase): - assert_no_exception(lambda: im.load()) - assert_equal(im.mode, '1') - assert_equal(im.size, (32, 32)) + def test_pil151(self): + from io import BytesIO + + im = Image.open(BytesIO(PIL151)) + + im.load() + self.assertEqual(im.mode, '1') + self.assertEqual(im.size, (32, 32)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_getpalette.py b/test/test_image_getpalette.py new file mode 100644 index 000000000..0c399c432 --- /dev/null +++ b/test/test_image_getpalette.py @@ -0,0 +1,26 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImageGetPalette(PillowTestCase): + + def test_palette(self): + def palette(mode): + p = lena(mode).getpalette() + if p: + return p[:10] + return None + self.assertEqual(palette("1"), None) + self.assertEqual(palette("L"), None) + self.assertEqual(palette("I"), None) + self.assertEqual(palette("F"), None) + self.assertEqual(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + self.assertEqual(palette("RGB"), None) + self.assertEqual(palette("RGBA"), None) + self.assertEqual(palette("CMYK"), None) + self.assertEqual(palette("YCbCr"), None) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_mode.py b/test/test_image_mode.py new file mode 100644 index 000000000..d229a27a2 --- /dev/null +++ b/test/test_image_mode.py @@ -0,0 +1,36 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImage(PillowTestCase): + + def test_sanity(self): + + im = lena() + im.mode + + def test_properties(self): + def check(mode, *result): + signature = ( + Image.getmodebase(mode), Image.getmodetype(mode), + Image.getmodebands(mode), Image.getmodebandnames(mode), + ) + self.assertEqual(signature, result) + check("1", "L", "L", 1, ("1",)) + check("L", "L", "L", 1, ("L",)) + check("P", "RGB", "L", 1, ("P",)) + check("I", "L", "I", 1, ("I",)) + check("F", "L", "F", 1, ("F",)) + check("RGB", "RGB", "L", 3, ("R", "G", "B")) + check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")) + check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) + check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) + check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")) + check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_image_quantize.py b/test/test_image_quantize.py new file mode 100644 index 000000000..35f876717 --- /dev/null +++ b/test/test_image_quantize.py @@ -0,0 +1,35 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + + +class TestImage(PillowTestCase): + + def test_sanity(self): + im = lena() + + im = im.quantize() + self.assert_image(im, "P", im.size) + + im = lena() + im = im.quantize(palette=lena("P")) + self.assert_image(im, "P", im.size) + + def test_octree_quantize(self): + im = lena() + + im = im.quantize(100, Image.FASTOCTREE) + self.assert_image(im, "P", im.size) + + assert len(im.getcolors()) == 100 + + def test_rgba_quantize(self): + im = lena('RGBA') + im.quantize() + self.assertRaises(Exception, lambda: im.quantize(method=0)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_imagewin.py b/test/test_imagewin.py index f7a6bc0dc..916abc77b 100644 --- a/test/test_imagewin.py +++ b/test/test_imagewin.py @@ -8,7 +8,7 @@ class TestImage(PillowTestCase): def test_sanity(self): dir(Image) - dir(ImageShow) + dir(ImageWin) pass From 5919c87afb1a6c6f1ab1d2787e0c79fee5e1ad5f Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 6 Jun 2014 17:13:29 +0300 Subject: [PATCH 098/488] Convert more tests --- Tests/test_image_getextrema.py | 17 ---- Tests/test_lib_pack.py | 138 ------------------------------- test/test_image_getextrema.py | 27 ++++++ test/test_lib_pack.py | 147 +++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 155 deletions(-) delete mode 100644 Tests/test_image_getextrema.py delete mode 100644 Tests/test_lib_pack.py create mode 100644 test/test_image_getextrema.py create mode 100644 test/test_lib_pack.py diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py deleted file mode 100644 index 86106cde0..000000000 --- a/Tests/test_image_getextrema.py +++ /dev/null @@ -1,17 +0,0 @@ -from tester import * - -from PIL import Image - -def test_extrema(): - - def extrema(mode): - return lena(mode).getextrema() - - assert_equal(extrema("1"), (0, 255)) - assert_equal(extrema("L"), (40, 235)) - assert_equal(extrema("I"), (40, 235)) - assert_equal(extrema("F"), (40.0, 235.0)) - assert_equal(extrema("P"), (11, 218)) # fixed palette - assert_equal(extrema("RGB"), ((61, 255), (26, 234), (44, 223))) - assert_equal(extrema("RGBA"), ((61, 255), (26, 234), (44, 223), (255, 255))) - assert_equal(extrema("CMYK"), ((0, 194), (21, 229), (32, 211), (0, 0))) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py deleted file mode 100644 index 7675348b3..000000000 --- a/Tests/test_lib_pack.py +++ /dev/null @@ -1,138 +0,0 @@ -from tester import * - -from PIL import Image - -def pack(): - pass # not yet - -def test_pack(): - - def pack(mode, rawmode): - if len(mode) == 1: - im = Image.new(mode, (1, 1), 1) - else: - im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) - - if py3: - return list(im.tobytes("raw", rawmode)) - else: - return [ord(c) for c in im.tobytes("raw", rawmode)] - - order = 1 if Image._ENDIAN == '<' else -1 - - assert_equal(pack("1", "1"), [128]) - assert_equal(pack("1", "1;I"), [0]) - assert_equal(pack("1", "1;R"), [1]) - assert_equal(pack("1", "1;IR"), [0]) - - assert_equal(pack("L", "L"), [1]) - - assert_equal(pack("I", "I"), [1, 0, 0, 0][::order]) - - assert_equal(pack("F", "F"), [0, 0, 128, 63][::order]) - - assert_equal(pack("LA", "LA"), [1, 2]) - - assert_equal(pack("RGB", "RGB"), [1, 2, 3]) - assert_equal(pack("RGB", "RGB;L"), [1, 2, 3]) - assert_equal(pack("RGB", "BGR"), [3, 2, 1]) - assert_equal(pack("RGB", "RGBX"), [1, 2, 3, 255]) # 255? - assert_equal(pack("RGB", "BGRX"), [3, 2, 1, 0]) - assert_equal(pack("RGB", "XRGB"), [0, 1, 2, 3]) - assert_equal(pack("RGB", "XBGR"), [0, 3, 2, 1]) - - assert_equal(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? - - assert_equal(pack("RGBA", "RGBA"), [1, 2, 3, 4]) - - assert_equal(pack("CMYK", "CMYK"), [1, 2, 3, 4]) - assert_equal(pack("YCbCr", "YCbCr"), [1, 2, 3]) - -def test_unpack(): - - def unpack(mode, rawmode, bytes_): - im = None - - if py3: - data = bytes(range(1,bytes_+1)) - else: - data = ''.join(chr(i) for i in range(1,bytes_+1)) - - im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1) - - return im.getpixel((0, 0)) - - def unpack_1(mode, rawmode, value): - assert mode == "1" - im = None - - if py3: - im = Image.frombytes(mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1) - else: - im = Image.frombytes(mode, (8, 1), chr(value), "raw", rawmode, 0, 1) - - return tuple(im.getdata()) - - X = 255 - - assert_equal(unpack_1("1", "1", 1), (0,0,0,0,0,0,0,X)) - assert_equal(unpack_1("1", "1;I", 1), (X,X,X,X,X,X,X,0)) - assert_equal(unpack_1("1", "1;R", 1), (X,0,0,0,0,0,0,0)) - assert_equal(unpack_1("1", "1;IR", 1), (0,X,X,X,X,X,X,X)) - - assert_equal(unpack_1("1", "1", 170), (X,0,X,0,X,0,X,0)) - assert_equal(unpack_1("1", "1;I", 170), (0,X,0,X,0,X,0,X)) - assert_equal(unpack_1("1", "1;R", 170), (0,X,0,X,0,X,0,X)) - assert_equal(unpack_1("1", "1;IR", 170), (X,0,X,0,X,0,X,0)) - - assert_equal(unpack("L", "L;2", 1), 0) - assert_equal(unpack("L", "L;4", 1), 0) - assert_equal(unpack("L", "L", 1), 1) - assert_equal(unpack("L", "L;I", 1), 254) - assert_equal(unpack("L", "L;R", 1), 128) - assert_equal(unpack("L", "L;16", 2), 2) # little endian - assert_equal(unpack("L", "L;16B", 2), 1) # big endian - - assert_equal(unpack("LA", "LA", 2), (1, 2)) - assert_equal(unpack("LA", "LA;L", 2), (1, 2)) - - assert_equal(unpack("RGB", "RGB", 3), (1, 2, 3)) - assert_equal(unpack("RGB", "RGB;L", 3), (1, 2, 3)) - assert_equal(unpack("RGB", "RGB;R", 3), (128, 64, 192)) - assert_equal(unpack("RGB", "RGB;16B", 6), (1, 3, 5)) # ? - assert_equal(unpack("RGB", "BGR", 3), (3, 2, 1)) - assert_equal(unpack("RGB", "RGB;15", 2), (8, 131, 0)) - assert_equal(unpack("RGB", "BGR;15", 2), (0, 131, 8)) - assert_equal(unpack("RGB", "RGB;16", 2), (8, 64, 0)) - assert_equal(unpack("RGB", "BGR;16", 2), (0, 64, 8)) - assert_equal(unpack("RGB", "RGB;4B", 2), (17, 0, 34)) - - assert_equal(unpack("RGB", "RGBX", 4), (1, 2, 3)) - assert_equal(unpack("RGB", "BGRX", 4), (3, 2, 1)) - assert_equal(unpack("RGB", "XRGB", 4), (2, 3, 4)) - assert_equal(unpack("RGB", "XBGR", 4), (4, 3, 2)) - - assert_equal(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) - assert_equal(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) - assert_equal(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) - assert_equal(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) - assert_equal(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) - assert_equal(unpack("RGBA", "BGRA;15", 2), (0, 131, 8, 0)) - assert_equal(unpack("RGBA", "RGBA;4B", 2), (17, 0, 34, 0)) - - assert_equal(unpack("RGBX", "RGBX", 4), (1, 2, 3, 4)) # 4->255? - assert_equal(unpack("RGBX", "BGRX", 4), (3, 2, 1, 255)) - assert_equal(unpack("RGBX", "XRGB", 4), (2, 3, 4, 255)) - assert_equal(unpack("RGBX", "XBGR", 4), (4, 3, 2, 255)) - assert_equal(unpack("RGBX", "RGB;15", 2), (8, 131, 0, 255)) - assert_equal(unpack("RGBX", "BGR;15", 2), (0, 131, 8, 255)) - assert_equal(unpack("RGBX", "RGB;4B", 2), (17, 0, 34, 255)) - - assert_equal(unpack("CMYK", "CMYK", 4), (1, 2, 3, 4)) - assert_equal(unpack("CMYK", "CMYK;I", 4), (254, 253, 252, 251)) - - assert_exception(ValueError, lambda: unpack("L", "L", 0)) - assert_exception(ValueError, lambda: unpack("RGB", "RGB", 2)) - assert_exception(ValueError, lambda: unpack("CMYK", "CMYK", 2)) - -run() diff --git a/test/test_image_getextrema.py b/test/test_image_getextrema.py new file mode 100644 index 000000000..af7f7698a --- /dev/null +++ b/test/test_image_getextrema.py @@ -0,0 +1,27 @@ +from helper import unittest, PillowTestCase, lena + + +class TestImageGetExtrema(PillowTestCase): + + def test_extrema(self): + + def extrema(mode): + return lena(mode).getextrema() + + self.assertEqual(extrema("1"), (0, 255)) + self.assertEqual(extrema("L"), (40, 235)) + self.assertEqual(extrema("I"), (40, 235)) + self.assertEqual(extrema("F"), (40.0, 235.0)) + self.assertEqual(extrema("P"), (11, 218)) # fixed palette + self.assertEqual( + extrema("RGB"), ((61, 255), (26, 234), (44, 223))) + self.assertEqual( + extrema("RGBA"), ((61, 255), (26, 234), (44, 223), (255, 255))) + self.assertEqual( + extrema("CMYK"), ((0, 194), (21, 229), (32, 211), (0, 0))) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/test/test_lib_pack.py b/test/test_lib_pack.py new file mode 100644 index 000000000..102835b58 --- /dev/null +++ b/test/test_lib_pack.py @@ -0,0 +1,147 @@ +from helper import unittest, PillowTestCase, py3 + +from PIL import Image + + +class TestLibPack(PillowTestCase): + + def pack(self): + pass # not yet + + def test_pack(self): + + def pack(mode, rawmode): + if len(mode) == 1: + im = Image.new(mode, (1, 1), 1) + else: + im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) + + if py3: + return list(im.tobytes("raw", rawmode)) + else: + return [ord(c) for c in im.tobytes("raw", rawmode)] + + order = 1 if Image._ENDIAN == '<' else -1 + + self.assertEqual(pack("1", "1"), [128]) + self.assertEqual(pack("1", "1;I"), [0]) + self.assertEqual(pack("1", "1;R"), [1]) + self.assertEqual(pack("1", "1;IR"), [0]) + + self.assertEqual(pack("L", "L"), [1]) + + self.assertEqual(pack("I", "I"), [1, 0, 0, 0][::order]) + + self.assertEqual(pack("F", "F"), [0, 0, 128, 63][::order]) + + self.assertEqual(pack("LA", "LA"), [1, 2]) + + self.assertEqual(pack("RGB", "RGB"), [1, 2, 3]) + self.assertEqual(pack("RGB", "RGB;L"), [1, 2, 3]) + self.assertEqual(pack("RGB", "BGR"), [3, 2, 1]) + self.assertEqual(pack("RGB", "RGBX"), [1, 2, 3, 255]) # 255? + self.assertEqual(pack("RGB", "BGRX"), [3, 2, 1, 0]) + self.assertEqual(pack("RGB", "XRGB"), [0, 1, 2, 3]) + self.assertEqual(pack("RGB", "XBGR"), [0, 3, 2, 1]) + + self.assertEqual(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? + + self.assertEqual(pack("RGBA", "RGBA"), [1, 2, 3, 4]) + + self.assertEqual(pack("CMYK", "CMYK"), [1, 2, 3, 4]) + self.assertEqual(pack("YCbCr", "YCbCr"), [1, 2, 3]) + + def test_unpack(self): + + def unpack(mode, rawmode, bytes_): + im = None + + if py3: + data = bytes(range(1, bytes_+1)) + else: + data = ''.join(chr(i) for i in range(1, bytes_+1)) + + im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1) + + return im.getpixel((0, 0)) + + def unpack_1(mode, rawmode, value): + assert mode == "1" + im = None + + if py3: + im = Image.frombytes( + mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1) + else: + im = Image.frombytes( + mode, (8, 1), chr(value), "raw", rawmode, 0, 1) + + return tuple(im.getdata()) + + X = 255 + + self.assertEqual(unpack_1("1", "1", 1), (0, 0, 0, 0, 0, 0, 0, X)) + self.assertEqual(unpack_1("1", "1;I", 1), (X, X, X, X, X, X, X, 0)) + self.assertEqual(unpack_1("1", "1;R", 1), (X, 0, 0, 0, 0, 0, 0, 0)) + self.assertEqual(unpack_1("1", "1;IR", 1), (0, X, X, X, X, X, X, X)) + + self.assertEqual(unpack_1("1", "1", 170), (X, 0, X, 0, X, 0, X, 0)) + self.assertEqual(unpack_1("1", "1;I", 170), (0, X, 0, X, 0, X, 0, X)) + self.assertEqual(unpack_1("1", "1;R", 170), (0, X, 0, X, 0, X, 0, X)) + self.assertEqual(unpack_1("1", "1;IR", 170), (X, 0, X, 0, X, 0, X, 0)) + + self.assertEqual(unpack("L", "L;2", 1), 0) + self.assertEqual(unpack("L", "L;4", 1), 0) + self.assertEqual(unpack("L", "L", 1), 1) + self.assertEqual(unpack("L", "L;I", 1), 254) + self.assertEqual(unpack("L", "L;R", 1), 128) + self.assertEqual(unpack("L", "L;16", 2), 2) # little endian + self.assertEqual(unpack("L", "L;16B", 2), 1) # big endian + + self.assertEqual(unpack("LA", "LA", 2), (1, 2)) + self.assertEqual(unpack("LA", "LA;L", 2), (1, 2)) + + self.assertEqual(unpack("RGB", "RGB", 3), (1, 2, 3)) + self.assertEqual(unpack("RGB", "RGB;L", 3), (1, 2, 3)) + self.assertEqual(unpack("RGB", "RGB;R", 3), (128, 64, 192)) + self.assertEqual(unpack("RGB", "RGB;16B", 6), (1, 3, 5)) # ? + self.assertEqual(unpack("RGB", "BGR", 3), (3, 2, 1)) + self.assertEqual(unpack("RGB", "RGB;15", 2), (8, 131, 0)) + self.assertEqual(unpack("RGB", "BGR;15", 2), (0, 131, 8)) + self.assertEqual(unpack("RGB", "RGB;16", 2), (8, 64, 0)) + self.assertEqual(unpack("RGB", "BGR;16", 2), (0, 64, 8)) + self.assertEqual(unpack("RGB", "RGB;4B", 2), (17, 0, 34)) + + self.assertEqual(unpack("RGB", "RGBX", 4), (1, 2, 3)) + self.assertEqual(unpack("RGB", "BGRX", 4), (3, 2, 1)) + self.assertEqual(unpack("RGB", "XRGB", 4), (2, 3, 4)) + self.assertEqual(unpack("RGB", "XBGR", 4), (4, 3, 2)) + + self.assertEqual(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) + self.assertEqual(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) + self.assertEqual(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) + self.assertEqual(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) + self.assertEqual(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) + self.assertEqual(unpack("RGBA", "BGRA;15", 2), (0, 131, 8, 0)) + self.assertEqual(unpack("RGBA", "RGBA;4B", 2), (17, 0, 34, 0)) + + self.assertEqual(unpack("RGBX", "RGBX", 4), (1, 2, 3, 4)) # 4->255? + self.assertEqual(unpack("RGBX", "BGRX", 4), (3, 2, 1, 255)) + self.assertEqual(unpack("RGBX", "XRGB", 4), (2, 3, 4, 255)) + self.assertEqual(unpack("RGBX", "XBGR", 4), (4, 3, 2, 255)) + self.assertEqual(unpack("RGBX", "RGB;15", 2), (8, 131, 0, 255)) + self.assertEqual(unpack("RGBX", "BGR;15", 2), (0, 131, 8, 255)) + self.assertEqual(unpack("RGBX", "RGB;4B", 2), (17, 0, 34, 255)) + + self.assertEqual(unpack("CMYK", "CMYK", 4), (1, 2, 3, 4)) + self.assertEqual(unpack("CMYK", "CMYK;I", 4), (254, 253, 252, 251)) + + self.assertRaises(ValueError, lambda: unpack("L", "L", 0)) + self.assertRaises(ValueError, lambda: unpack("RGB", "RGB", 2)) + self.assertRaises(ValueError, lambda: unpack("CMYK", "CMYK", 2)) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 9415407b833d025a7153443d39b6a688deaaa82f Mon Sep 17 00:00:00 2001 From: Ben Williams Date: Fri, 6 Jun 2014 21:42:20 +0100 Subject: [PATCH 099/488] Fix a its/it's incorrect usage --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index e064ed9ef..fe4f47295 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -522,7 +522,7 @@ class Image: """ Closes the file pointer, if possible. - This operation will destroy the image core and release it's memory. + This operation will destroy the image core and release its memory. The image data will be unusable afterward. This function is only required to close images that have not From 8693c63173928fb18bdd82fb7f3a450ddab95bfd Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 7 Jun 2014 00:23:55 +0300 Subject: [PATCH 100/488] assert_warning() checks in case of multiple warnings --- test/helper.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/helper.py b/test/helper.py index 54739cf7b..567fc3945 100644 --- a/test/helper.py +++ b/test/helper.py @@ -69,9 +69,13 @@ class PillowTestCase(unittest.TestCase): result = func() # Verify some things. - self.assertEqual(len(w), 1) - assert issubclass(w[-1].category, warn_class) - self.assertIn("deprecated", str(w[-1].message)) + self.assertGreaterEqual(len(w), 1) + found = False + for v in w: + if issubclass(v.category, warn_class): + found = True + break + self.assertTrue(found) return result # # require that deprecation warnings are triggered From 1d96522932927f18eb2b7f3b35608ab94a9a86ce Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 8 Jun 2014 07:02:58 -0400 Subject: [PATCH 101/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 71a42e315..40d8ed5cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Use unittest for tests + [hugovk] + - ImageCms fixes [hugovk] From b7f94e3609b027f108b75f89bf63f07e54262d7d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 9 Jun 2014 20:03:57 -0400 Subject: [PATCH 102/488] Add test runner --- setup.py | 2 +- test/__init__.py | 0 test/helper.py | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 test/__init__.py diff --git a/setup.py b/setup.py index 83defd82b..45bddf937 100644 --- a/setup.py +++ b/setup.py @@ -680,7 +680,7 @@ setup( include_package_data=True, packages=find_packages(), scripts=glob.glob("Scripts/pil*.py"), - test_suite='PIL.tests', + test_suite='test.helper.TestSuite', keywords=["Imaging",], license='Standard PIL License', zip_safe=True, diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/helper.py b/test/helper.py index 567fc3945..588b4f0ec 100644 --- a/test/helper.py +++ b/test/helper.py @@ -308,3 +308,6 @@ def lena(mode="RGB", cache={}): # # # _setup() + +TestLoader = unittest.TestLoader() +TestSuite = TestLoader.discover(start_dir='.') From 7a10f6b39a2c1f191c977571e69ceafe8022c006 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 9 Jun 2014 20:21:31 -0400 Subject: [PATCH 103/488] Experimental import fix Why would this work local but not on travis? --- test/test_000_sanity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_000_sanity.py b/test/test_000_sanity.py index 22e582ec3..fda481ed1 100644 --- a/test/test_000_sanity.py +++ b/test/test_000_sanity.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase import PIL import PIL.Image From 0c47ce00b1d9e4801a2043e0e14ba62e896da2a9 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 9 Jun 2014 20:36:40 -0400 Subject: [PATCH 104/488] Fix broken py3 tests --- test/test_bmp_reference.py | 2 +- test/test_file_fli.py | 2 +- test/test_file_icns.py | 2 +- test/test_file_ico.py | 2 +- test/test_file_psd.py | 2 +- test/test_file_xbm.py | 2 +- test/test_file_xpm.py | 2 +- test/test_font_bdf.py | 2 +- test/test_format_lab.py | 2 +- test/test_image_array.py | 2 +- test/test_image_copy.py | 2 +- test/test_image_crop.py | 2 +- test/test_image_filter.py | 2 +- test/test_image_frombytes.py | 2 +- test/test_image_getbands.py | 2 +- test/test_image_getbbox.py | 2 +- test/test_image_getcolors.py | 2 +- test/test_image_getextrema.py | 2 +- test/test_image_getim.py | 2 +- test/test_image_getpalette.py | 2 +- test/test_image_histogram.py | 2 +- test/test_image_load.py | 2 +- test/test_image_mode.py | 2 +- test/test_image_offset.py | 2 +- test/test_image_putalpha.py | 2 +- test/test_image_putdata.py | 2 +- test/test_image_putpalette.py | 2 +- test/test_image_quantize.py | 2 +- test/test_image_resize.py | 2 +- test/test_image_rotate.py | 2 +- test/test_image_thumbnail.py | 2 +- test/test_image_tobitmap.py | 2 +- test/test_image_tobytes.py | 2 +- test/test_image_transform.py | 2 +- test/test_imagechops.py | 2 +- test/test_imagecms.py | 2 +- test/test_imagecolor.py | 2 +- test/test_imagedraw.py | 2 +- test/test_imageenhance.py | 2 +- test/test_imagefileio.py | 2 +- test/test_imagefilter.py | 2 +- test/test_imagefont.py | 2 +- test/test_imagegrab.py | 2 +- test/test_imagemath.py | 2 +- test/test_imagemode.py | 2 +- test/test_imageops.py | 2 +- test/test_imageshow.py | 2 +- test/test_imagestat.py | 2 +- test/test_imagetk.py | 2 +- test/test_imagetransform.py | 2 +- test/test_imagewin.py | 2 +- test/test_lib_image.py | 2 +- test/test_lib_pack.py | 2 +- test/test_locale.py | 2 +- 54 files changed, 54 insertions(+), 54 deletions(-) diff --git a/test/test_bmp_reference.py b/test/test_bmp_reference.py index ed012e7c8..ce621b38a 100644 --- a/test/test_bmp_reference.py +++ b/test/test_bmp_reference.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image import os diff --git a/test/test_file_fli.py b/test/test_file_fli.py index dd22a58f9..3479a4271 100644 --- a/test/test_file_fli.py +++ b/test/test_file_fli.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_icns.py b/test/test_file_icns.py index 9d838620b..d65af2ecf 100644 --- a/test/test_file_icns.py +++ b/test/test_file_icns.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_ico.py b/test/test_file_ico.py index dc289e1d2..9e524561c 100644 --- a/test/test_file_ico.py +++ b/test/test_file_ico.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_psd.py b/test/test_file_psd.py index de3d6f33d..b30b95bff 100644 --- a/test/test_file_psd.py +++ b/test/test_file_psd.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_xbm.py b/test/test_file_xbm.py index 02aec70b1..0a4f50a49 100644 --- a/test/test_file_xbm.py +++ b/test/test_file_xbm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_xpm.py b/test/test_file_xpm.py index ecbb4137a..c74232812 100644 --- a/test/test_file_xpm.py +++ b/test/test_file_xpm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_font_bdf.py b/test/test_font_bdf.py index b141e6149..9e6a38dd9 100644 --- a/test/test_font_bdf.py +++ b/test/test_font_bdf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import FontFile, BdfFontFile diff --git a/test/test_format_lab.py b/test/test_format_lab.py index 53468db5f..36e84801e 100644 --- a/test/test_format_lab.py +++ b/test/test_format_lab.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_image_array.py b/test/test_image_array.py index a0f5f29e1..3e30019fe 100644 --- a/test/test_image_array.py +++ b/test/test_image_array.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_copy.py b/test/test_image_copy.py index a7882db94..7c668a464 100644 --- a/test/test_image_copy.py +++ b/test/test_image_copy.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_crop.py b/test/test_image_crop.py index da93fe7c8..bcb097e25 100644 --- a/test/test_image_crop.py +++ b/test/test_image_crop.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_filter.py b/test/test_image_filter.py index 4a85b0a2e..373d48356 100644 --- a/test/test_image_filter.py +++ b/test/test_image_filter.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageFilter diff --git a/test/test_image_frombytes.py b/test/test_image_frombytes.py index aad8046a1..92885f9a9 100644 --- a/test/test_image_frombytes.py +++ b/test/test_image_frombytes.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_getbands.py b/test/test_image_getbands.py index e803abb02..03ff3914b 100644 --- a/test/test_image_getbands.py +++ b/test/test_image_getbands.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_image_getbbox.py b/test/test_image_getbbox.py index 83a6a3dec..1f01bcee3 100644 --- a/test/test_image_getbbox.py +++ b/test/test_image_getbbox.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_getcolors.py b/test/test_image_getcolors.py index cfc827b28..13c10ad86 100644 --- a/test/test_image_getcolors.py +++ b/test/test_image_getcolors.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImage(PillowTestCase): diff --git a/test/test_image_getextrema.py b/test/test_image_getextrema.py index af7f7698a..77521294e 100644 --- a/test/test_image_getextrema.py +++ b/test/test_image_getextrema.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImageGetExtrema(PillowTestCase): diff --git a/test/test_image_getim.py b/test/test_image_getim.py index d498d3923..47283d97b 100644 --- a/test/test_image_getim.py +++ b/test/test_image_getim.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena, py3 +from test.helper import unittest, PillowTestCase, lena, py3 class TestImageGetIm(PillowTestCase): diff --git a/test/test_image_getpalette.py b/test/test_image_getpalette.py index 0c399c432..d9184412a 100644 --- a/test/test_image_getpalette.py +++ b/test/test_image_getpalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImageGetPalette(PillowTestCase): diff --git a/test/test_image_histogram.py b/test/test_image_histogram.py index 70f78a1fb..4a0981009 100644 --- a/test/test_image_histogram.py +++ b/test/test_image_histogram.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImageHistogram(PillowTestCase): diff --git a/test/test_image_load.py b/test/test_image_load.py index 2001c233a..9c6f09388 100644 --- a/test/test_image_load.py +++ b/test/test_image_load.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_mode.py b/test/test_image_mode.py index d229a27a2..2c1bac609 100644 --- a/test/test_image_mode.py +++ b/test/test_image_mode.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_offset.py b/test/test_image_offset.py index bb9b5a38c..146e0af9c 100644 --- a/test/test_image_offset.py +++ b/test/test_image_offset.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImage(PillowTestCase): diff --git a/test/test_image_putalpha.py b/test/test_image_putalpha.py index 85c7ac262..0ea1215f9 100644 --- a/test/test_image_putalpha.py +++ b/test/test_image_putalpha.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_image_putdata.py b/test/test_image_putdata.py index c7c3115aa..be8e6bc22 100644 --- a/test/test_image_putdata.py +++ b/test/test_image_putdata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena import sys diff --git a/test/test_image_putpalette.py b/test/test_image_putpalette.py index b77dcbf00..d6daaa42b 100644 --- a/test/test_image_putpalette.py +++ b/test/test_image_putpalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import ImagePalette diff --git a/test/test_image_quantize.py b/test/test_image_quantize.py index 35f876717..c1bbdd134 100644 --- a/test/test_image_quantize.py +++ b/test/test_image_quantize.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_resize.py b/test/test_image_resize.py index 6c9932e45..62cd971c5 100644 --- a/test/test_image_resize.py +++ b/test/test_image_resize.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImageResize(PillowTestCase): diff --git a/test/test_image_rotate.py b/test/test_image_rotate.py index 531fdd63f..e00b5a4c5 100644 --- a/test/test_image_rotate.py +++ b/test/test_image_rotate.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImageRotate(PillowTestCase): diff --git a/test/test_image_thumbnail.py b/test/test_image_thumbnail.py index ee49be43e..1ab06d360 100644 --- a/test/test_image_thumbnail.py +++ b/test/test_image_thumbnail.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena class TestImageThumbnail(PillowTestCase): diff --git a/test/test_image_tobitmap.py b/test/test_image_tobitmap.py index 4e2a16df0..e7a66e712 100644 --- a/test/test_image_tobitmap.py +++ b/test/test_image_tobitmap.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena, fromstring +from test.helper import unittest, PillowTestCase, lena, fromstring class TestImage(PillowTestCase): diff --git a/test/test_image_tobytes.py b/test/test_image_tobytes.py index 3be9128c1..e1bfa29fd 100644 --- a/test/test_image_tobytes.py +++ b/test/test_image_tobytes.py @@ -1,4 +1,4 @@ -from helper import unittest, lena +from test.helper import unittest, lena class TestImageToBytes(unittest.TestCase): diff --git a/test/test_image_transform.py b/test/test_image_transform.py index 6ab186c12..1cf84c521 100644 --- a/test/test_image_transform.py +++ b/test/test_image_transform.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_imagechops.py b/test/test_imagechops.py index ec162d52f..5ffc02feb 100644 --- a/test/test_imagechops.py +++ b/test/test_imagechops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageChops diff --git a/test/test_imagecms.py b/test/test_imagecms.py index 1a31636e8..7e59184dd 100644 --- a/test/test_imagecms.py +++ b/test/test_imagecms.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_imagecolor.py b/test/test_imagecolor.py index 5d8944852..2c41d0765 100644 --- a/test/test_imagecolor.py +++ b/test/test_imagecolor.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, lena from PIL import Image from PIL import ImageColor diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py index 98876296f..9d5d575c5 100644 --- a/test/test_imagedraw.py +++ b/test/test_imagedraw.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageColor diff --git a/test/test_imageenhance.py b/test/test_imageenhance.py index 433c49cf6..d05f94655 100644 --- a/test/test_imageenhance.py +++ b/test/test_imageenhance.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageEnhance diff --git a/test/test_imagefileio.py b/test/test_imagefileio.py index 32ee0bc5e..4f96181ca 100644 --- a/test/test_imagefileio.py +++ b/test/test_imagefileio.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena, tostring +from test.helper import unittest, PillowTestCase, lena, tostring from PIL import Image from PIL import ImageFileIO diff --git a/test/test_imagefilter.py b/test/test_imagefilter.py index f7edb409a..19bd1a874 100644 --- a/test/test_imagefilter.py +++ b/test/test_imagefilter.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import ImageFilter diff --git a/test/test_imagefont.py b/test/test_imagefont.py index 17cb38cc2..2024ef2eb 100644 --- a/test/test_imagefont.py +++ b/test/test_imagefont.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image from PIL import ImageDraw diff --git a/test/test_imagegrab.py b/test/test_imagegrab.py index 2275d34a1..a3c7db9fc 100644 --- a/test/test_imagegrab.py +++ b/test/test_imagegrab.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase try: from PIL import ImageGrab diff --git a/test/test_imagemath.py b/test/test_imagemath.py index 17d43d25a..06b6dea08 100644 --- a/test/test_imagemath.py +++ b/test/test_imagemath.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image from PIL import ImageMath diff --git a/test/test_imagemode.py b/test/test_imagemode.py index 7fb596b46..c8480ec3f 100644 --- a/test/test_imagemode.py +++ b/test/test_imagemode.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import ImageMode diff --git a/test/test_imageops.py b/test/test_imageops.py index a4a94ca4d..e904b06ce 100644 --- a/test/test_imageops.py +++ b/test/test_imageops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import ImageOps diff --git a/test/test_imageshow.py b/test/test_imageshow.py index 12594c98f..27af375e7 100644 --- a/test/test_imageshow.py +++ b/test/test_imageshow.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image from PIL import ImageShow diff --git a/test/test_imagestat.py b/test/test_imagestat.py index 4d30ff023..b15ee8000 100644 --- a/test/test_imagestat.py +++ b/test/test_imagestat.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase from PIL import Image from PIL import ImageStat diff --git a/test/test_imagetk.py b/test/test_imagetk.py index 87a07e288..aa10df0f4 100644 --- a/test/test_imagetk.py +++ b/test/test_imagetk.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase class TestImageTk(PillowTestCase): diff --git a/test/test_imagetransform.py b/test/test_imagetransform.py index f5741df32..5d7fc657f 100644 --- a/test/test_imagetransform.py +++ b/test/test_imagetransform.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image from PIL import ImageTransform diff --git a/test/test_imagewin.py b/test/test_imagewin.py index 916abc77b..a3ca7ee98 100644 --- a/test/test_imagewin.py +++ b/test/test_imagewin.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageWin diff --git a/test/test_lib_image.py b/test/test_lib_image.py index e0a903b00..99d1aa9b0 100644 --- a/test/test_lib_image.py +++ b/test/test_lib_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from test.helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_lib_pack.py b/test/test_lib_pack.py index 102835b58..d0162dbcb 100644 --- a/test/test_lib_pack.py +++ b/test/test_lib_pack.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, py3 +from test.helper import unittest, PillowTestCase, py3 from PIL import Image diff --git a/test/test_locale.py b/test/test_locale.py index 599e46266..77774dc31 100644 --- a/test/test_locale.py +++ b/test/test_locale.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, lena +from test.helper import unittest, PillowTestCase, lena from PIL import Image From 7a866e743137b67336b090ea641125f0d94cb91c Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 9 Jun 2014 20:56:29 -0400 Subject: [PATCH 105/488] Revert "Fix broken py3 tests" This reverts commit 0c47ce00b1d9e4801a2043e0e14ba62e896da2a9. --- test/test_bmp_reference.py | 2 +- test/test_file_fli.py | 2 +- test/test_file_icns.py | 2 +- test/test_file_ico.py | 2 +- test/test_file_psd.py | 2 +- test/test_file_xbm.py | 2 +- test/test_file_xpm.py | 2 +- test/test_font_bdf.py | 2 +- test/test_format_lab.py | 2 +- test/test_image_array.py | 2 +- test/test_image_copy.py | 2 +- test/test_image_crop.py | 2 +- test/test_image_filter.py | 2 +- test/test_image_frombytes.py | 2 +- test/test_image_getbands.py | 2 +- test/test_image_getbbox.py | 2 +- test/test_image_getcolors.py | 2 +- test/test_image_getextrema.py | 2 +- test/test_image_getim.py | 2 +- test/test_image_getpalette.py | 2 +- test/test_image_histogram.py | 2 +- test/test_image_load.py | 2 +- test/test_image_mode.py | 2 +- test/test_image_offset.py | 2 +- test/test_image_putalpha.py | 2 +- test/test_image_putdata.py | 2 +- test/test_image_putpalette.py | 2 +- test/test_image_quantize.py | 2 +- test/test_image_resize.py | 2 +- test/test_image_rotate.py | 2 +- test/test_image_thumbnail.py | 2 +- test/test_image_tobitmap.py | 2 +- test/test_image_tobytes.py | 2 +- test/test_image_transform.py | 2 +- test/test_imagechops.py | 2 +- test/test_imagecms.py | 2 +- test/test_imagecolor.py | 2 +- test/test_imagedraw.py | 2 +- test/test_imageenhance.py | 2 +- test/test_imagefileio.py | 2 +- test/test_imagefilter.py | 2 +- test/test_imagefont.py | 2 +- test/test_imagegrab.py | 2 +- test/test_imagemath.py | 2 +- test/test_imagemode.py | 2 +- test/test_imageops.py | 2 +- test/test_imageshow.py | 2 +- test/test_imagestat.py | 2 +- test/test_imagetk.py | 2 +- test/test_imagetransform.py | 2 +- test/test_imagewin.py | 2 +- test/test_lib_image.py | 2 +- test/test_lib_pack.py | 2 +- test/test_locale.py | 2 +- 54 files changed, 54 insertions(+), 54 deletions(-) diff --git a/test/test_bmp_reference.py b/test/test_bmp_reference.py index ce621b38a..ed012e7c8 100644 --- a/test/test_bmp_reference.py +++ b/test/test_bmp_reference.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image import os diff --git a/test/test_file_fli.py b/test/test_file_fli.py index 3479a4271..dd22a58f9 100644 --- a/test/test_file_fli.py +++ b/test/test_file_fli.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_icns.py b/test/test_file_icns.py index d65af2ecf..9d838620b 100644 --- a/test/test_file_icns.py +++ b/test/test_file_icns.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_ico.py b/test/test_file_ico.py index 9e524561c..dc289e1d2 100644 --- a/test/test_file_ico.py +++ b/test/test_file_ico.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_psd.py b/test/test_file_psd.py index b30b95bff..de3d6f33d 100644 --- a/test/test_file_psd.py +++ b/test/test_file_psd.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_xbm.py b/test/test_file_xbm.py index 0a4f50a49..02aec70b1 100644 --- a/test/test_file_xbm.py +++ b/test/test_file_xbm.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_file_xpm.py b/test/test_file_xpm.py index c74232812..ecbb4137a 100644 --- a/test/test_file_xpm.py +++ b/test/test_file_xpm.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_font_bdf.py b/test/test_font_bdf.py index 9e6a38dd9..b141e6149 100644 --- a/test/test_font_bdf.py +++ b/test/test_font_bdf.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import FontFile, BdfFontFile diff --git a/test/test_format_lab.py b/test/test_format_lab.py index 36e84801e..53468db5f 100644 --- a/test/test_format_lab.py +++ b/test/test_format_lab.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_image_array.py b/test/test_image_array.py index 3e30019fe..a0f5f29e1 100644 --- a/test/test_image_array.py +++ b/test/test_image_array.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_copy.py b/test/test_image_copy.py index 7c668a464..a7882db94 100644 --- a/test/test_image_copy.py +++ b/test/test_image_copy.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_crop.py b/test/test_image_crop.py index bcb097e25..da93fe7c8 100644 --- a/test/test_image_crop.py +++ b/test/test_image_crop.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_filter.py b/test/test_image_filter.py index 373d48356..4a85b0a2e 100644 --- a/test/test_image_filter.py +++ b/test/test_image_filter.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageFilter diff --git a/test/test_image_frombytes.py b/test/test_image_frombytes.py index 92885f9a9..aad8046a1 100644 --- a/test/test_image_frombytes.py +++ b/test/test_image_frombytes.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_getbands.py b/test/test_image_getbands.py index 03ff3914b..e803abb02 100644 --- a/test/test_image_getbands.py +++ b/test/test_image_getbands.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_image_getbbox.py b/test/test_image_getbbox.py index 1f01bcee3..83a6a3dec 100644 --- a/test/test_image_getbbox.py +++ b/test/test_image_getbbox.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_getcolors.py b/test/test_image_getcolors.py index 13c10ad86..cfc827b28 100644 --- a/test/test_image_getcolors.py +++ b/test/test_image_getcolors.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImage(PillowTestCase): diff --git a/test/test_image_getextrema.py b/test/test_image_getextrema.py index 77521294e..af7f7698a 100644 --- a/test/test_image_getextrema.py +++ b/test/test_image_getextrema.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageGetExtrema(PillowTestCase): diff --git a/test/test_image_getim.py b/test/test_image_getim.py index 47283d97b..d498d3923 100644 --- a/test/test_image_getim.py +++ b/test/test_image_getim.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena, py3 +from helper import unittest, PillowTestCase, lena, py3 class TestImageGetIm(PillowTestCase): diff --git a/test/test_image_getpalette.py b/test/test_image_getpalette.py index d9184412a..0c399c432 100644 --- a/test/test_image_getpalette.py +++ b/test/test_image_getpalette.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageGetPalette(PillowTestCase): diff --git a/test/test_image_histogram.py b/test/test_image_histogram.py index 4a0981009..70f78a1fb 100644 --- a/test/test_image_histogram.py +++ b/test/test_image_histogram.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageHistogram(PillowTestCase): diff --git a/test/test_image_load.py b/test/test_image_load.py index 9c6f09388..2001c233a 100644 --- a/test/test_image_load.py +++ b/test/test_image_load.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_mode.py b/test/test_image_mode.py index 2c1bac609..d229a27a2 100644 --- a/test/test_image_mode.py +++ b/test/test_image_mode.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_offset.py b/test/test_image_offset.py index 146e0af9c..bb9b5a38c 100644 --- a/test/test_image_offset.py +++ b/test/test_image_offset.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImage(PillowTestCase): diff --git a/test/test_image_putalpha.py b/test/test_image_putalpha.py index 0ea1215f9..85c7ac262 100644 --- a/test/test_image_putalpha.py +++ b/test/test_image_putalpha.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_image_putdata.py b/test/test_image_putdata.py index be8e6bc22..c7c3115aa 100644 --- a/test/test_image_putdata.py +++ b/test/test_image_putdata.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena import sys diff --git a/test/test_image_putpalette.py b/test/test_image_putpalette.py index d6daaa42b..b77dcbf00 100644 --- a/test/test_image_putpalette.py +++ b/test/test_image_putpalette.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import ImagePalette diff --git a/test/test_image_quantize.py b/test/test_image_quantize.py index c1bbdd134..35f876717 100644 --- a/test/test_image_quantize.py +++ b/test/test_image_quantize.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_image_resize.py b/test/test_image_resize.py index 62cd971c5..6c9932e45 100644 --- a/test/test_image_resize.py +++ b/test/test_image_resize.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageResize(PillowTestCase): diff --git a/test/test_image_rotate.py b/test/test_image_rotate.py index e00b5a4c5..531fdd63f 100644 --- a/test/test_image_rotate.py +++ b/test/test_image_rotate.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageRotate(PillowTestCase): diff --git a/test/test_image_thumbnail.py b/test/test_image_thumbnail.py index 1ab06d360..ee49be43e 100644 --- a/test/test_image_thumbnail.py +++ b/test/test_image_thumbnail.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena class TestImageThumbnail(PillowTestCase): diff --git a/test/test_image_tobitmap.py b/test/test_image_tobitmap.py index e7a66e712..4e2a16df0 100644 --- a/test/test_image_tobitmap.py +++ b/test/test_image_tobitmap.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena, fromstring +from helper import unittest, PillowTestCase, lena, fromstring class TestImage(PillowTestCase): diff --git a/test/test_image_tobytes.py b/test/test_image_tobytes.py index e1bfa29fd..3be9128c1 100644 --- a/test/test_image_tobytes.py +++ b/test/test_image_tobytes.py @@ -1,4 +1,4 @@ -from test.helper import unittest, lena +from helper import unittest, lena class TestImageToBytes(unittest.TestCase): diff --git a/test/test_image_transform.py b/test/test_image_transform.py index 1cf84c521..6ab186c12 100644 --- a/test/test_image_transform.py +++ b/test/test_image_transform.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_imagechops.py b/test/test_imagechops.py index 5ffc02feb..ec162d52f 100644 --- a/test/test_imagechops.py +++ b/test/test_imagechops.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageChops diff --git a/test/test_imagecms.py b/test/test_imagecms.py index 7e59184dd..1a31636e8 100644 --- a/test/test_imagecms.py +++ b/test/test_imagecms.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/test/test_imagecolor.py b/test/test_imagecolor.py index 2c41d0765..5d8944852 100644 --- a/test/test_imagecolor.py +++ b/test/test_imagecolor.py @@ -1,4 +1,4 @@ -from test.helper import unittest, lena +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageColor diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py index 9d5d575c5..98876296f 100644 --- a/test/test_imagedraw.py +++ b/test/test_imagedraw.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageColor diff --git a/test/test_imageenhance.py b/test/test_imageenhance.py index d05f94655..433c49cf6 100644 --- a/test/test_imageenhance.py +++ b/test/test_imageenhance.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageEnhance diff --git a/test/test_imagefileio.py b/test/test_imagefileio.py index 4f96181ca..32ee0bc5e 100644 --- a/test/test_imagefileio.py +++ b/test/test_imagefileio.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena, tostring +from helper import unittest, PillowTestCase, lena, tostring from PIL import Image from PIL import ImageFileIO diff --git a/test/test_imagefilter.py b/test/test_imagefilter.py index 19bd1a874..f7edb409a 100644 --- a/test/test_imagefilter.py +++ b/test/test_imagefilter.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import ImageFilter diff --git a/test/test_imagefont.py b/test/test_imagefont.py index 2024ef2eb..17cb38cc2 100644 --- a/test/test_imagefont.py +++ b/test/test_imagefont.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageDraw diff --git a/test/test_imagegrab.py b/test/test_imagegrab.py index a3c7db9fc..2275d34a1 100644 --- a/test/test_imagegrab.py +++ b/test/test_imagegrab.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase try: from PIL import ImageGrab diff --git a/test/test_imagemath.py b/test/test_imagemath.py index 06b6dea08..17d43d25a 100644 --- a/test/test_imagemath.py +++ b/test/test_imagemath.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageMath diff --git a/test/test_imagemode.py b/test/test_imagemode.py index c8480ec3f..7fb596b46 100644 --- a/test/test_imagemode.py +++ b/test/test_imagemode.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import ImageMode diff --git a/test/test_imageops.py b/test/test_imageops.py index e904b06ce..a4a94ca4d 100644 --- a/test/test_imageops.py +++ b/test/test_imageops.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import ImageOps diff --git a/test/test_imageshow.py b/test/test_imageshow.py index 27af375e7..12594c98f 100644 --- a/test/test_imageshow.py +++ b/test/test_imageshow.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageShow diff --git a/test/test_imagestat.py b/test/test_imagestat.py index b15ee8000..4d30ff023 100644 --- a/test/test_imagestat.py +++ b/test/test_imagestat.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageStat diff --git a/test/test_imagetk.py b/test/test_imagetk.py index aa10df0f4..87a07e288 100644 --- a/test/test_imagetk.py +++ b/test/test_imagetk.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase class TestImageTk(PillowTestCase): diff --git a/test/test_imagetransform.py b/test/test_imagetransform.py index 5d7fc657f..f5741df32 100644 --- a/test/test_imagetransform.py +++ b/test/test_imagetransform.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageTransform diff --git a/test/test_imagewin.py b/test/test_imagewin.py index a3ca7ee98..916abc77b 100644 --- a/test/test_imagewin.py +++ b/test/test_imagewin.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageWin diff --git a/test/test_lib_image.py b/test/test_lib_image.py index 99d1aa9b0..e0a903b00 100644 --- a/test/test_lib_image.py +++ b/test/test_lib_image.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase from PIL import Image diff --git a/test/test_lib_pack.py b/test/test_lib_pack.py index d0162dbcb..102835b58 100644 --- a/test/test_lib_pack.py +++ b/test/test_lib_pack.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, py3 +from helper import unittest, PillowTestCase, py3 from PIL import Image diff --git a/test/test_locale.py b/test/test_locale.py index 77774dc31..599e46266 100644 --- a/test/test_locale.py +++ b/test/test_locale.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, lena from PIL import Image From e7bd48fa5ee8299cfa74355d0b98ac178a336ff1 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 9 Jun 2014 20:56:32 -0400 Subject: [PATCH 106/488] Revert "Experimental import fix" This reverts commit 7a10f6b39a2c1f191c977571e69ceafe8022c006. --- test/test_000_sanity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_000_sanity.py b/test/test_000_sanity.py index fda481ed1..22e582ec3 100644 --- a/test/test_000_sanity.py +++ b/test/test_000_sanity.py @@ -1,4 +1,4 @@ -from test.helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase import PIL import PIL.Image From a78b713d88c0e9ed061ca44c85638fd44c4946aa Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 9 Jun 2014 20:56:34 -0400 Subject: [PATCH 107/488] Revert "Add test runner" This reverts commit b7f94e3609b027f108b75f89bf63f07e54262d7d. --- setup.py | 2 +- test/__init__.py | 0 test/helper.py | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 test/__init__.py diff --git a/setup.py b/setup.py index 45bddf937..83defd82b 100644 --- a/setup.py +++ b/setup.py @@ -680,7 +680,7 @@ setup( include_package_data=True, packages=find_packages(), scripts=glob.glob("Scripts/pil*.py"), - test_suite='test.helper.TestSuite', + test_suite='PIL.tests', keywords=["Imaging",], license='Standard PIL License', zip_safe=True, diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/helper.py b/test/helper.py index 588b4f0ec..567fc3945 100644 --- a/test/helper.py +++ b/test/helper.py @@ -308,6 +308,3 @@ def lena(mode="RGB", cache={}): # # # _setup() - -TestLoader = unittest.TestLoader() -TestSuite = TestLoader.discover(start_dir='.') From e8ef927bacad29bebd48c44da6982a1f452f7326 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 12:05:46 +0300 Subject: [PATCH 108/488] Remove test_001_archive.py as it doesn't do anything: ../pil-archive/* doesn't exist --- Tests/test_001_archive.py | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 Tests/test_001_archive.py diff --git a/Tests/test_001_archive.py b/Tests/test_001_archive.py deleted file mode 100644 index a914a6c4c..000000000 --- a/Tests/test_001_archive.py +++ /dev/null @@ -1,23 +0,0 @@ -import PIL -import PIL.Image - -import glob, os - -for file in glob.glob("../pil-archive/*"): - f, e = os.path.splitext(file) - if e in [".txt", ".ttf", ".otf", ".zip"]: - continue - try: - im = PIL.Image.open(file) - im.load() - except IOError as v: - print("-", "failed to open", file, "-", v) - else: - print("+", file, im.mode, im.size, im.format) - if e == ".exif": - try: - info = im._getexif() - except KeyError as v: - print("-", "failed to get exif info from", file, "-", v) - -print("ok") From c66f332679f83f13fdc7ae86d67578ca7c7ebbe8 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 12:09:47 +0300 Subject: [PATCH 109/488] Remove empty tests --- Tests/test_image_paste.py | 5 ----- Tests/test_image_save.py | 5 ----- Tests/test_image_seek.py | 5 ----- Tests/test_image_show.py | 5 ----- Tests/test_image_tell.py | 5 ----- Tests/test_image_verify.py | 5 ----- 6 files changed, 30 deletions(-) delete mode 100644 Tests/test_image_paste.py delete mode 100644 Tests/test_image_save.py delete mode 100644 Tests/test_image_seek.py delete mode 100644 Tests/test_image_show.py delete mode 100644 Tests/test_image_tell.py delete mode 100644 Tests/test_image_verify.py diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_paste.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_save.py b/Tests/test_image_save.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_save.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_seek.py b/Tests/test_image_seek.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_seek.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_show.py b/Tests/test_image_show.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_show.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_tell.py b/Tests/test_image_tell.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_tell.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() diff --git a/Tests/test_image_verify.py b/Tests/test_image_verify.py deleted file mode 100644 index 7d4b6d9b3..000000000 --- a/Tests/test_image_verify.py +++ /dev/null @@ -1,5 +0,0 @@ -from tester import * - -from PIL import Image - -success() From 3ec505958ed6225c7d0d6481bf4a6bad5d2a3324 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 12:10:47 +0300 Subject: [PATCH 110/488] Convert old tests to use unittest --- Tests/test_000_sanity.py | 40 ++- Tests/test_bmp_reference.py | 156 +++++----- Tests/test_cffi.py | 197 +++++++----- Tests/test_file_bmp.py | 64 ++-- Tests/test_file_eps.py | 201 +++++++------ Tests/test_file_fli.py | 23 +- Tests/test_file_gif.py | 147 ++++----- Tests/test_file_icns.py | 114 +++---- Tests/test_file_ico.py | 23 +- Tests/test_file_jpeg.py | 427 +++++++++++++------------- Tests/test_file_jpeg2k.py | 174 +++++------ Tests/test_file_libtiff.py | 484 ++++++++++++++++-------------- Tests/test_file_libtiff_small.py | 82 ++--- Tests/test_file_msp.py | 27 +- Tests/test_file_pcx.py | 67 +++-- Tests/test_file_pdf.py | 99 +++--- Tests/test_file_png.py | 356 +++++++++++----------- Tests/test_file_ppm.py | 54 ++-- Tests/test_file_psd.py | 23 +- Tests/test_file_spider.py | 53 ++-- Tests/test_file_tar.py | 46 +-- Tests/test_file_tiff.py | 245 +++++++-------- Tests/test_file_tiff_metadata.py | 137 +++++---- Tests/test_file_webp.py | 117 ++++---- Tests/test_file_webp_alpha.py | 143 ++++----- Tests/test_file_webp_lossless.py | 46 +-- Tests/test_file_webp_metadata.py | 201 +++++++------ Tests/test_file_xbm.py | 22 +- Tests/test_file_xpm.py | 23 +- Tests/test_font_bdf.py | 23 +- Tests/test_font_pcf.py | 76 +++-- Tests/test_format_lab.py | 63 ++-- Tests/test_image.py | 66 ++-- Tests/test_image_array.py | 67 +++-- Tests/test_image_convert.py | 238 +++++++-------- Tests/test_image_copy.py | 26 +- Tests/test_image_crop.py | 83 ++--- Tests/test_image_draft.py | 43 +-- Tests/test_image_filter.py | 139 +++++---- Tests/test_image_frombytes.py | 18 +- Tests/test_image_getbands.py | 33 +- Tests/test_image_getbbox.py | 55 ++-- Tests/test_image_getcolors.py | 106 ++++--- Tests/test_image_getdata.py | 47 +-- Tests/test_image_getextrema.py | 36 ++- Tests/test_image_getim.py | 21 +- Tests/test_image_getpalette.py | 41 +-- Tests/test_image_getpixel.py | 62 ++-- Tests/test_image_getprojection.py | 50 +-- Tests/test_image_histogram.py | 37 ++- Tests/test_image_load.py | 40 ++- Tests/test_image_mode.py | 53 ++-- Tests/test_image_offset.py | 29 +- Tests/test_image_point.py | 66 ++-- Tests/test_image_putalpha.py | 65 ++-- Tests/test_image_putdata.py | 62 ++-- Tests/test_image_putpalette.py | 56 ++-- Tests/test_image_putpixel.py | 79 ++--- Tests/test_image_quantize.py | 42 +-- Tests/test_image_resize.py | 27 +- Tests/test_image_rotate.py | 33 +- Tests/test_image_split.py | 102 ++++--- Tests/test_image_thumbnail.py | 57 ++-- Tests/test_image_tobitmap.py | 25 +- Tests/test_image_tobytes.py | 16 +- Tests/test_image_transform.py | 229 +++++++------- Tests/test_image_transpose.py | 42 ++- Tests/test_imagechops.py | 100 +++--- Tests/test_imagecms.py | 373 ++++++++++++----------- Tests/test_imagecolor.py | 101 ++++--- Tests/test_imagedraw.py | 359 +++++++++++----------- Tests/test_imageenhance.py | 31 +- Tests/test_imagefile.py | 125 ++++---- Tests/test_imagefileio.py | 37 ++- Tests/test_imagefilter.py | 52 ++-- Tests/test_imagefont.py | 258 ++++++++-------- Tests/test_imagegrab.py | 26 +- Tests/test_imagemath.py | 90 +++--- Tests/test_imagemode.py | 45 +-- Tests/test_imageops.py | 114 +++---- Tests/test_imageops_usm.py | 87 +++--- Tests/test_imagepalette.py | 52 ++-- Tests/test_imagepath.py | 96 +++--- Tests/test_imageqt.py | 72 +++-- Tests/test_imagesequence.py | 33 +- Tests/test_imageshow.py | 16 +- Tests/test_imagestat.py | 83 ++--- Tests/test_imagetk.py | 22 +- Tests/test_imagetransform.py | 33 +- Tests/test_imagewin.py | 16 +- Tests/test_lib_image.py | 55 ++-- Tests/test_lib_pack.py | 213 ++++++------- Tests/test_locale.py | 44 +-- Tests/test_mode_i16.py | 202 +++++++------ Tests/test_numpy.py | 206 +++++++------ Tests/test_olefileio.py | 295 +++++++++--------- Tests/test_pickle.py | 123 ++++---- 97 files changed, 5162 insertions(+), 4341 deletions(-) diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index a30786458..1ad76cc50 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -1,24 +1,32 @@ -from __future__ import print_function -from tester import * +from helper import unittest, PillowTestCase, tearDownModule import PIL import PIL.Image -# Make sure we have the binary extension -im = PIL.Image.core.new("L", (100, 100)) -assert PIL.Image.VERSION[:3] == '1.1' +class TestSanity(PillowTestCase): -# Create an image and do stuff with it. -im = PIL.Image.new("1", (100, 100)) -assert (im.mode, im.size) == ('1', (100, 100)) -assert len(im.tobytes()) == 1300 + def test_sanity(self): -# Create images in all remaining major modes. -im = PIL.Image.new("L", (100, 100)) -im = PIL.Image.new("P", (100, 100)) -im = PIL.Image.new("RGB", (100, 100)) -im = PIL.Image.new("I", (100, 100)) -im = PIL.Image.new("F", (100, 100)) + # Make sure we have the binary extension + im = PIL.Image.core.new("L", (100, 100)) -print("ok") + self.assertEqual(PIL.Image.VERSION[:3], '1.1') + + # Create an image and do stuff with it. + im = PIL.Image.new("1", (100, 100)) + self.assertEqual((im.mode, im.size), ('1', (100, 100))) + self.assertEqual(len(im.tobytes()), 1300) + + # Create images in all remaining major modes. + im = PIL.Image.new("L", (100, 100)) + im = PIL.Image.new("P", (100, 100)) + im = PIL.Image.new("RGB", (100, 100)) + im = PIL.Image.new("I", (100, 100)) + im = PIL.Image.new("F", (100, 100)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 99818229f..c8d93983b 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image import os @@ -6,81 +6,89 @@ import os base = os.path.join('Tests', 'images', 'bmp') -def get_files(d, ext='.bmp'): - return [os.path.join(base,d,f) for f - in os.listdir(os.path.join(base, d)) if ext in f] +class TestBmpReference(PillowTestCase): -def test_bad(): - """ These shouldn't crash/dos, but they shouldn't return anything either """ - for f in get_files('b'): - try: - im = Image.open(f) - im.load() - except Exception as msg: - pass - # print ("Bad Image %s: %s" %(f,msg)) + def get_files(self, d, ext='.bmp'): + return [os.path.join(base, d, f) for f + in os.listdir(os.path.join(base, d)) if ext in f] -def test_questionable(): - """ These shouldn't crash/dos, but its not well defined that these are in spec """ - for f in get_files('q'): - try: - im = Image.open(f) - im.load() - except Exception as msg: - pass - # print ("Bad Image %s: %s" %(f,msg)) + def test_bad(self): + """ These shouldn't crash/dos, but they shouldn't return anything + either """ + for f in self.get_files('b'): + try: + im = Image.open(f) + im.load() + except Exception: # as msg: + pass + # print ("Bad Image %s: %s" %(f,msg)) + + def test_questionable(self): + """ These shouldn't crash/dos, but its not well defined that these + are in spec """ + for f in self.get_files('q'): + try: + im = Image.open(f) + im.load() + except Exception: # as msg: + pass + # print ("Bad Image %s: %s" %(f,msg)) + + def test_good(self): + """ These should all work. There's a set of target files in the + html directory that we can compare against. """ + + # Target files, if they're not just replacing the extension + file_map = {'pal1wb.bmp': 'pal1.png', + 'pal4rle.bmp': 'pal4.png', + 'pal8-0.bmp': 'pal8.png', + 'pal8rle.bmp': 'pal8.png', + 'pal8topdown.bmp': 'pal8.png', + 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', + 'pal8os2.bmp': 'pal8.png', + 'pal8os2sp.bmp': 'pal8.png', + 'pal8os2v2.bmp': 'pal8.png', + 'pal8os2v2-16.bmp': 'pal8.png', + 'pal8v4.bmp': 'pal8.png', + 'pal8v5.bmp': 'pal8.png', + 'rgb16-565pal.bmp': 'rgb16-565.png', + 'rgb24pal.bmp': 'rgb24.png', + 'rgb32.bmp': 'rgb24.png', + 'rgb32bf.bmp': 'rgb24.png' + } + + def get_compare(f): + (head, name) = os.path.split(f) + if name in file_map: + return os.path.join(base, 'html', file_map[name]) + (name, ext) = os.path.splitext(name) + return os.path.join(base, 'html', "%s.png" % name) + + for f in self.get_files('g'): + try: + im = Image.open(f) + im.load() + compare = Image.open(get_compare(f)) + compare.load() + if im.mode == 'P': + # assert image similar doesn't really work + # with paletized image, since the palette might + # be differently ordered for an equivalent image. + im = im.convert('RGBA') + compare = im.convert('RGBA') + self.assert_image_similar(im, compare, 5) + + except Exception as msg: + # there are three here that are unsupported: + unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), + os.path.join(base, 'g', 'pal8rle.bmp'), + os.path.join(base, 'g', 'pal4rle.bmp')) + if f not in unsupported: + self.assertTrue( + False, "Unsupported Image %s: %s" % (f, msg)) -def test_good(): - """ These should all work. There's a set of target files in the - html directory that we can compare against. """ - - # Target files, if they're not just replacing the extension - file_map = { 'pal1wb.bmp': 'pal1.png', - 'pal4rle.bmp': 'pal4.png', - 'pal8-0.bmp': 'pal8.png', - 'pal8rle.bmp': 'pal8.png', - 'pal8topdown.bmp': 'pal8.png', - 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', - 'pal8os2.bmp': 'pal8.png', - 'pal8os2sp.bmp': 'pal8.png', - 'pal8os2v2.bmp': 'pal8.png', - 'pal8os2v2-16.bmp': 'pal8.png', - 'pal8v4.bmp': 'pal8.png', - 'pal8v5.bmp': 'pal8.png', - 'rgb16-565pal.bmp': 'rgb16-565.png', - 'rgb24pal.bmp': 'rgb24.png', - 'rgb32.bmp': 'rgb24.png', - 'rgb32bf.bmp': 'rgb24.png' - } - - def get_compare(f): - (head, name) = os.path.split(f) - if name in file_map: - return os.path.join(base, 'html', file_map[name]) - (name,ext) = os.path.splitext(name) - return os.path.join(base, 'html', "%s.png"%name) - - for f in get_files('g'): - try: - im = Image.open(f) - im.load() - compare = Image.open(get_compare(f)) - compare.load() - if im.mode == 'P': - # assert image similar doesn't really work - # with paletized image, since the palette might - # be differently ordered for an equivalent image. - im = im.convert('RGBA') - compare = im.convert('RGBA') - assert_image_similar(im, compare,5) - - - except Exception as msg: - # there are three here that are unsupported: - unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), - os.path.join(base, 'g', 'pal8rle.bmp'), - os.path.join(base, 'g', 'pal4rle.bmp')) - if f not in unsupported: - assert_true(False, "Unsupported Image %s: %s" %(f,msg)) +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 1c0d8d31e..8ff4e817f 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -1,99 +1,136 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena try: import cffi + from PIL import PyAccess except: - skip() - -from PIL import Image, PyAccess + # Skip in setUp() + pass -import test_image_putpixel as put -import test_image_getpixel as get +from PIL import Image +from test_image_putpixel import TestImagePutPixel +from test_image_getpixel import TestImageGetPixel Image.USE_CFFI_ACCESS = True -def test_put(): - put.test_sanity() -def test_get(): - get.test_basic() - get.test_signedness() +class TestCffiPutPixel(TestImagePutPixel): -def _test_get_access(im): - """ Do we get the same thing as the old pixel access """ + def setUp(self): + try: + import cffi + except: + self.skipTest("No cffi") - """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - - w,h = im.size - for x in range(0,w,10): - for y in range(0,h,10): - assert_equal(access[(x,y)], caccess[(x,y)]) - -def test_get_vs_c(): - _test_get_access(lena('RGB')) - _test_get_access(lena('RGBA')) - _test_get_access(lena('L')) - _test_get_access(lena('LA')) - _test_get_access(lena('1')) - _test_get_access(lena('P')) - #_test_get_access(lena('PA')) # PA -- how do I make a PA image??? - _test_get_access(lena('F')) - - im = Image.new('I;16', (10,10), 40000) - _test_get_access(im) - im = Image.new('I;16L', (10,10), 40000) - _test_get_access(im) - im = Image.new('I;16B', (10,10), 40000) - _test_get_access(im) - - im = Image.new('I', (10,10), 40000) - _test_get_access(im) - # These don't actually appear to be modes that I can actually make, - # as unpack sets them directly into the I mode. - #im = Image.new('I;32L', (10,10), -2**10) - #_test_get_access(im) - #im = Image.new('I;32B', (10,10), 2**10) - #_test_get_access(im) + def test_put(self): + self.test_sanity() +class TestCffiGetPixel(TestImageGetPixel): -def _test_set_access(im, color): - """ Are we writing the correct bits into the image? """ + def setUp(self): + try: + import cffi + except: + self.skipTest("No cffi") - """ Using private interfaces, forcing a capi access and a pyaccess for the same image """ - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) + def test_get(self): + self.test_basic() + self.test_signedness() - w,h = im.size - for x in range(0,w,10): - for y in range(0,h,10): - access[(x,y)] = color - assert_equal(color, caccess[(x,y)]) -def test_set_vs_c(): - _test_set_access(lena('RGB'), (255, 128,0) ) - _test_set_access(lena('RGBA'), (255, 192, 128, 0)) - _test_set_access(lena('L'), 128) - _test_set_access(lena('LA'), (128,128)) - _test_set_access(lena('1'), 255) - _test_set_access(lena('P') , 128) - ##_test_set_access(i, (128,128)) #PA -- undone how to make - _test_set_access(lena('F'), 1024.0) - - im = Image.new('I;16', (10,10), 40000) - _test_set_access(im, 45000) - im = Image.new('I;16L', (10,10), 40000) - _test_set_access(im, 45000) - im = Image.new('I;16B', (10,10), 40000) - _test_set_access(im, 45000) - +class TestCffi(PillowTestCase): - im = Image.new('I', (10,10), 40000) - _test_set_access(im, 45000) -# im = Image.new('I;32L', (10,10), -(2**10)) -# _test_set_access(im, -(2**13)+1) - #im = Image.new('I;32B', (10,10), 2**10) - #_test_set_access(im, 2**13-1) + def setUp(self): + try: + import cffi + except: + self.skipTest("No cffi") + + def _test_get_access(self, im): + """ Do we get the same thing as the old pixel access """ + + """ Using private interfaces, forcing a capi access and + a pyaccess for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w, h = im.size + for x in range(0, w, 10): + for y in range(0, h, 10): + self.assertEqual(access[(x, y)], caccess[(x, y)]) + + def test_get_vs_c(self): + rgb = lena('RGB') + rgb.load() + self._test_get_access(rgb) + self._test_get_access(lena('RGBA')) + self._test_get_access(lena('L')) + self._test_get_access(lena('LA')) + self._test_get_access(lena('1')) + self._test_get_access(lena('P')) + # self._test_get_access(lena('PA')) # PA -- how do I make a PA image? + self._test_get_access(lena('F')) + + im = Image.new('I;16', (10, 10), 40000) + self._test_get_access(im) + im = Image.new('I;16L', (10, 10), 40000) + self._test_get_access(im) + im = Image.new('I;16B', (10, 10), 40000) + self._test_get_access(im) + + im = Image.new('I', (10, 10), 40000) + self._test_get_access(im) + # These don't actually appear to be modes that I can actually make, + # as unpack sets them directly into the I mode. + # im = Image.new('I;32L', (10, 10), -2**10) + # self._test_get_access(im) + # im = Image.new('I;32B', (10, 10), 2**10) + # self._test_get_access(im) + + def _test_set_access(self, im, color): + """ Are we writing the correct bits into the image? """ + + """ Using private interfaces, forcing a capi access and + a pyaccess for the same image """ + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) + + w, h = im.size + for x in range(0, w, 10): + for y in range(0, h, 10): + access[(x, y)] = color + self.assertEqual(color, caccess[(x, y)]) + + def test_set_vs_c(self): + rgb = lena('RGB') + rgb.load() + self._test_set_access(rgb, (255, 128, 0)) + self._test_set_access(lena('RGBA'), (255, 192, 128, 0)) + self._test_set_access(lena('L'), 128) + self._test_set_access(lena('LA'), (128, 128)) + self._test_set_access(lena('1'), 255) + self._test_set_access(lena('P'), 128) + # self._test_set_access(i, (128, 128)) #PA -- undone how to make + self._test_set_access(lena('F'), 1024.0) + + im = Image.new('I;16', (10, 10), 40000) + self._test_set_access(im, 45000) + im = Image.new('I;16L', (10, 10), 40000) + self._test_set_access(im, 45000) + im = Image.new('I;16B', (10, 10), 40000) + self._test_set_access(im, 45000) + + im = Image.new('I', (10, 10), 40000) + self._test_set_access(im, 45000) + # im = Image.new('I;32L', (10, 10), -(2**10)) + # self._test_set_access(im, -(2**13)+1) + # im = Image.new('I;32B', (10, 10), 2**10) + # self._test_set_access(im, 2**13-1) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index dd5f31fd2..29ddb9824 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,38 +1,44 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image import io -def roundtrip(im): - outfile = tempfile("temp.bmp") - im.save(outfile, 'BMP') +class TestFileBmp(PillowTestCase): - reloaded = Image.open(outfile) - reloaded.load() - assert_equal(im.mode, reloaded.mode) - assert_equal(im.size, reloaded.size) - assert_equal(reloaded.format, "BMP") + def roundtrip(self, im): + outfile = self.tempfile("temp.bmp") + + im.save(outfile, 'BMP') + + reloaded = Image.open(outfile) + reloaded.load() + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual(im.size, reloaded.size) + self.assertEqual(reloaded.format, "BMP") + + def test_sanity(self): + self.roundtrip(lena()) + + self.roundtrip(lena("1")) + self.roundtrip(lena("L")) + self.roundtrip(lena("P")) + self.roundtrip(lena("RGB")) + + def test_save_to_bytes(self): + output = io.BytesIO() + im = lena() + im.save(output, "BMP") + + output.seek(0) + reloaded = Image.open(output) + + self.assertEqual(im.mode, reloaded.mode) + self.assertEqual(im.size, reloaded.size) + self.assertEqual(reloaded.format, "BMP") -def test_sanity(): - roundtrip(lena()) - - roundtrip(lena("1")) - roundtrip(lena("L")) - roundtrip(lena("P")) - roundtrip(lena("RGB")) +if __name__ == '__main__': + unittest.main() - -def test_save_to_bytes(): - output = io.BytesIO() - im = lena() - im.save(output, "BMP") - - output.seek(0) - reloaded = Image.open(output) - - assert_equal(im.mode, reloaded.mode) - assert_equal(im.size, reloaded.size) - assert_equal(reloaded.format, "BMP") - +# End of file diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 34ece8c8b..6a1a1b5e2 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,11 +1,8 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image, EpsImagePlugin import io -if not EpsImagePlugin.has_ghostscript(): - skip() - # Our two EPS test files (they are identical except for their bounding boxes) file1 = "Tests/images/zero_bb.eps" file2 = "Tests/images/non_zero_bb.eps" @@ -20,123 +17,127 @@ file2_compare_scale2 = "Tests/images/non_zero_bb_scale2.png" # EPS test files with binary preview file3 = "Tests/images/binary_preview_map.eps" -def test_sanity(): - # Regular scale - image1 = Image.open(file1) - image1.load() - assert_equal(image1.mode, "RGB") - assert_equal(image1.size, (460, 352)) - assert_equal(image1.format, "EPS") - image2 = Image.open(file2) - image2.load() - assert_equal(image2.mode, "RGB") - assert_equal(image2.size, (360, 252)) - assert_equal(image2.format, "EPS") +class TestFileEps(PillowTestCase): - # Double scale - image1_scale2 = Image.open(file1) - image1_scale2.load(scale=2) - assert_equal(image1_scale2.mode, "RGB") - assert_equal(image1_scale2.size, (920, 704)) - assert_equal(image1_scale2.format, "EPS") + def setUp(self): + if not EpsImagePlugin.has_ghostscript(): + self.skipTest("Ghostscript not available") - image2_scale2 = Image.open(file2) - image2_scale2.load(scale=2) - assert_equal(image2_scale2.mode, "RGB") - assert_equal(image2_scale2.size, (720, 504)) - assert_equal(image2_scale2.format, "EPS") + def test_sanity(self): + # Regular scale + image1 = Image.open(file1) + image1.load() + self.assertEqual(image1.mode, "RGB") + self.assertEqual(image1.size, (460, 352)) + self.assertEqual(image1.format, "EPS") + image2 = Image.open(file2) + image2.load() + self.assertEqual(image2.mode, "RGB") + self.assertEqual(image2.size, (360, 252)) + self.assertEqual(image2.format, "EPS") -def test_file_object(): - # issue 479 - image1 = Image.open(file1) - with open(tempfile('temp_file.eps'), 'wb') as fh: - image1.save(fh, 'EPS') + # Double scale + image1_scale2 = Image.open(file1) + image1_scale2.load(scale=2) + self.assertEqual(image1_scale2.mode, "RGB") + self.assertEqual(image1_scale2.size, (920, 704)) + self.assertEqual(image1_scale2.format, "EPS") + image2_scale2 = Image.open(file2) + image2_scale2.load(scale=2) + self.assertEqual(image2_scale2.mode, "RGB") + self.assertEqual(image2_scale2.size, (720, 504)) + self.assertEqual(image2_scale2.format, "EPS") -def test_iobase_object(): - # issue 479 - image1 = Image.open(file1) - with io.open(tempfile('temp_iobase.eps'), 'wb') as fh: - image1.save(fh, 'EPS') + def test_file_object(self): + # issue 479 + image1 = Image.open(file1) + with open(self.tempfile('temp_file.eps'), 'wb') as fh: + image1.save(fh, 'EPS') + def test_iobase_object(self): + # issue 479 + image1 = Image.open(file1) + with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh: + image1.save(fh, 'EPS') -def test_render_scale1(): - # We need png support for these render test - codecs = dir(Image.core) - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - skip("zip/deflate support not available") + def test_render_scale1(self): + # We need png support for these render test + codecs = dir(Image.core) + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + self.skipTest("zip/deflate support not available") - # Zero bounding box - image1_scale1 = Image.open(file1) - image1_scale1.load() - image1_scale1_compare = Image.open(file1_compare).convert("RGB") - image1_scale1_compare.load() - assert_image_similar(image1_scale1, image1_scale1_compare, 5) + # Zero bounding box + image1_scale1 = Image.open(file1) + image1_scale1.load() + image1_scale1_compare = Image.open(file1_compare).convert("RGB") + image1_scale1_compare.load() + self.assert_image_similar(image1_scale1, image1_scale1_compare, 5) - # Non-Zero bounding box - image2_scale1 = Image.open(file2) - image2_scale1.load() - image2_scale1_compare = Image.open(file2_compare).convert("RGB") - image2_scale1_compare.load() - assert_image_similar(image2_scale1, image2_scale1_compare, 10) + # Non-Zero bounding box + image2_scale1 = Image.open(file2) + image2_scale1.load() + image2_scale1_compare = Image.open(file2_compare).convert("RGB") + image2_scale1_compare.load() + self.assert_image_similar(image2_scale1, image2_scale1_compare, 10) + def test_render_scale2(self): + # We need png support for these render test + codecs = dir(Image.core) + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + self.skipTest("zip/deflate support not available") -def test_render_scale2(): - # We need png support for these render test - codecs = dir(Image.core) - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - skip("zip/deflate support not available") + # Zero bounding box + image1_scale2 = Image.open(file1) + image1_scale2.load(scale=2) + image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") + image1_scale2_compare.load() + self.assert_image_similar(image1_scale2, image1_scale2_compare, 5) - # Zero bounding box - image1_scale2 = Image.open(file1) - image1_scale2.load(scale=2) - image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") - image1_scale2_compare.load() - assert_image_similar(image1_scale2, image1_scale2_compare, 5) + # Non-Zero bounding box + image2_scale2 = Image.open(file2) + image2_scale2.load(scale=2) + image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") + image2_scale2_compare.load() + self.assert_image_similar(image2_scale2, image2_scale2_compare, 10) - # Non-Zero bounding box - image2_scale2 = Image.open(file2) - image2_scale2.load(scale=2) - image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") - image2_scale2_compare.load() - assert_image_similar(image2_scale2, image2_scale2_compare, 10) + def test_resize(self): + # Arrange + image1 = Image.open(file1) + image2 = Image.open(file2) + new_size = (100, 100) + # Act + image1 = image1.resize(new_size) + image2 = image2.resize(new_size) -def test_resize(): - # Arrange - image1 = Image.open(file1) - image2 = Image.open(file2) - new_size = (100, 100) + # Assert + self.assertEqual(image1.size, new_size) + self.assertEqual(image2.size, new_size) - # Act - image1 = image1.resize(new_size) - image2 = image2.resize(new_size) + def test_thumbnail(self): + # Issue #619 + # Arrange + image1 = Image.open(file1) + image2 = Image.open(file2) + new_size = (100, 100) - # Assert - assert_equal(image1.size, new_size) - assert_equal(image2.size, new_size) + # Act + image1.thumbnail(new_size) + image2.thumbnail(new_size) + # Assert + self.assertEqual(max(image1.size), max(new_size)) + self.assertEqual(max(image2.size), max(new_size)) -def test_thumbnail(): - # Issue #619 - # Arrange - image1 = Image.open(file1) - image2 = Image.open(file2) - new_size = (100, 100) + def test_read_binary_preview(self): + # Issue 302 + # open image with binary preview + Image.open(file3) - # Act - image1.thumbnail(new_size) - image2.thumbnail(new_size) - - # Assert - assert_equal(max(image1.size), max(new_size)) - assert_equal(max(image2.size), max(new_size)) - -def test_read_binary_preview(): - # Issue 302 - # open image with binary preview - image1 = Image.open(file3) +if __name__ == '__main__': + unittest.main() # End of file diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 4e06a732e..787365c14 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -6,9 +6,18 @@ from PIL import Image file = "Images/lena.fli" data = open(file, "rb").read() -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "P") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "FLI") + +class TestFileFli(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "FLI") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 566baa200..3aefbe9b3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,87 +1,96 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image codecs = dir(Image.core) -if "gif_encoder" not in codecs or "gif_decoder" not in codecs: - skip("gif support not available") # can this happen? - # sample gif stream file = "Images/lena.gif" with open(file, "rb") as f: data = f.read() -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "P") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "GIF") -def test_optimize(): - def test(optimize): - im = Image.new("L", (1, 1), 0) - file = BytesIO() - im.save(file, "GIF", optimize=optimize) - return len(file.getvalue()) - assert_equal(test(0), 800) - assert_equal(test(1), 38) +class TestFileGif(PillowTestCase): -def test_roundtrip(): - out = tempfile('temp.gif') - im = lena() - im.save(out) - reread = Image.open(out) + def setUp(self): + if "gif_encoder" not in codecs or "gif_decoder" not in codecs: + self.skipTest("gif support not available") # can this happen? - assert_image_similar(reread.convert('RGB'), im, 50) + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "GIF") -def test_roundtrip2(): - #see https://github.com/python-pillow/Pillow/issues/403 - out = tempfile('temp.gif') - im = Image.open('Images/lena.gif') - im2 = im.copy() - im2.save(out) - reread = Image.open(out) + def test_optimize(self): + from io import BytesIO - assert_image_similar(reread.convert('RGB'), lena(), 50) + def test(optimize): + im = Image.new("L", (1, 1), 0) + file = BytesIO() + im.save(file, "GIF", optimize=optimize) + return len(file.getvalue()) + self.assertEqual(test(0), 800) + self.assertEqual(test(1), 38) + + def test_roundtrip(self): + out = self.tempfile('temp.gif') + im = lena() + im.save(out) + reread = Image.open(out) + + self.assert_image_similar(reread.convert('RGB'), im, 50) + + def test_roundtrip2(self): + # see https://github.com/python-pillow/Pillow/issues/403 + out = self.tempfile('temp.gif') + im = Image.open('Images/lena.gif') + im2 = im.copy() + im2.save(out) + reread = Image.open(out) + + self.assert_image_similar(reread.convert('RGB'), lena(), 50) + + def test_palette_handling(self): + # see https://github.com/python-pillow/Pillow/issues/513 + + im = Image.open('Images/lena.gif') + im = im.convert('RGB') + + im = im.resize((100, 100), Image.ANTIALIAS) + im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) + + f = self.tempfile('temp.gif') + im2.save(f, optimize=True) + + reloaded = Image.open(f) + + self.assert_image_similar(im, reloaded.convert('RGB'), 10) + + def test_palette_434(self): + # see https://github.com/python-pillow/Pillow/issues/434 + + def roundtrip(im, *args, **kwargs): + out = self.tempfile('temp.gif') + im.save(out, *args, **kwargs) + reloaded = Image.open(out) + + return [im, reloaded] + + orig = "Tests/images/test.colors.gif" + im = Image.open(orig) + + self.assert_image_equal(*roundtrip(im)) + self.assert_image_equal(*roundtrip(im, optimize=True)) + + im = im.convert("RGB") + # check automatic P conversion + reloaded = roundtrip(im)[1].convert('RGB') + self.assert_image_equal(im, reloaded) -def test_palette_handling(): - # see https://github.com/python-pillow/Pillow/issues/513 - - im = Image.open('Images/lena.gif') - im = im.convert('RGB') - - im = im.resize((100,100), Image.ANTIALIAS) - im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) - - f = tempfile('temp.gif') - im2.save(f, optimize=True) - - reloaded = Image.open(f) - - assert_image_similar(im, reloaded.convert('RGB'), 10) - -def test_palette_434(): - # see https://github.com/python-pillow/Pillow/issues/434 - - def roundtrip(im, *args, **kwargs): - out = tempfile('temp.gif') - im.save(out, *args, **kwargs) - reloaded = Image.open(out) - - return [im, reloaded] - - orig = "Tests/images/test.colors.gif" - im = Image.open(orig) - - assert_image_equal(*roundtrip(im)) - assert_image_equal(*roundtrip(im, optimize=True)) - - im = im.convert("RGB") - # check automatic P conversion - reloaded = roundtrip(im)[1].convert('RGB') - assert_image_equal(im, reloaded) - +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 3e31f8879..c307ec844 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -8,59 +8,67 @@ data = open(file, "rb").read() enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') -def test_sanity(): - # Loading this icon by default should result in the largest size - # (512x512@2x) being loaded - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGBA") - assert_equal(im.size, (1024, 1024)) - assert_equal(im.format, "ICNS") -def test_sizes(): - # Check that we can load all of the sizes, and that the final pixel - # dimensions are as expected - im = Image.open(file) - for w,h,r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open(file) - im2.size = (w, h, r) - im2.load() - assert_equal(im2.mode, 'RGBA') - assert_equal(im2.size, (wr, hr)) +class TestFileIcns(PillowTestCase): -def test_older_icon(): - # This icon was made with Icon Composer rather than iconutil; it still - # uses PNG rather than JP2, however (since it was made on 10.9). - im = Image.open('Tests/images/pillow2.icns') - for w,h,r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open('Tests/images/pillow2.icns') - im2.size = (w, h, r) - im2.load() - assert_equal(im2.mode, 'RGBA') - assert_equal(im2.size, (wr, hr)) + def test_sanity(self): + # Loading this icon by default should result in the largest size + # (512x512@2x) being loaded + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (1024, 1024)) + self.assertEqual(im.format, "ICNS") -def test_jp2_icon(): - # This icon was made by using Uli Kusterer's oldiconutil to replace - # the PNG images with JPEG 2000 ones. The advantage of doing this is - # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial - # software therefore does just this. - - # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) + def test_sizes(self): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + im = Image.open(file) + for w, h, r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open(file) + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.size, (wr, hr)) - if not enable_jpeg2k: - return - - im = Image.open('Tests/images/pillow3.icns') - for w,h,r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open('Tests/images/pillow3.icns') - im2.size = (w, h, r) - im2.load() - assert_equal(im2.mode, 'RGBA') - assert_equal(im2.size, (wr, hr)) - + def test_older_icon(self): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + im = Image.open('Tests/images/pillow2.icns') + for w, h, r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow2.icns') + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.size, (wr, hr)) + + def test_jp2_icon(self): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. + + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) + + if not enable_jpeg2k: + return + + im = Image.open('Tests/images/pillow3.icns') + for w, h, r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow3.icns') + im2.size = (w, h, r) + im2.load() + self.assertEqual(im2.mode, 'RGBA') + self.assertEqual(im2.size, (wr, hr)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index e0db34acc..5bad94a53 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -6,9 +6,18 @@ from PIL import Image file = "Images/lena.ico" data = open(file, "rb").read() -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGBA") - assert_equal(im.size, (16, 16)) - assert_equal(im.format, "ICO") + +class TestFileIco(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (16, 16)) + self.assertEqual(im.format, "ICO") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 43ed55969..c21910962 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,238 +1,231 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena, py3 import random +from io import BytesIO from PIL import Image from PIL import ImageFile codecs = dir(Image.core) -if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - skip("jpeg support not available") - test_file = "Images/lena.jpg" -def roundtrip(im, **options): - out = BytesIO() - im.save(out, "JPEG", **options) - bytes = out.tell() - out.seek(0) - im = Image.open(out) - im.bytes = bytes # for testing only - return im +class TestFileJpeg(PillowTestCase): -# -------------------------------------------------------------------- + def setUp(self): + if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: + self.skipTest("jpeg support not available") + def roundtrip(self, im, **options): + out = BytesIO() + im.save(out, "JPEG", **options) + bytes = out.tell() + out.seek(0) + im = Image.open(out) + im.bytes = bytes # for testing only + return im -def test_sanity(): + def test_sanity(self): - # internal version number - assert_match(Image.core.jpeglib_version, "\d+\.\d+$") + # internal version number + self.assertRegexpMatches(Image.core.jpeglib_version, "\d+\.\d+$") - im = Image.open(test_file) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "JPEG") - - -# -------------------------------------------------------------------- - -def test_app(): - # Test APP/COM reader (@PIL135) - im = Image.open(test_file) - assert_equal(im.applist[0], - ("APP0", b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00")) - assert_equal(im.applist[1], ("COM", b"Python Imaging Library")) - assert_equal(len(im.applist), 2) - - -def test_cmyk(): - # Test CMYK handling. Thanks to Tim and Charlie for test data, - # Michael for getting me to look one more time. - f = "Tests/images/pil_sample_cmyk.jpg" - im = Image.open(f) - # the source image has red pixels in the upper left corner. - c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] - assert_true(c == 0.0 and m > 0.8 and y > 0.8 and k == 0.0) - # the opposite corner is black - c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))] - assert_true(k > 0.9) - # roundtrip, and check again - im = roundtrip(im) - c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] - assert_true(c == 0.0 and m > 0.8 and y > 0.8 and k == 0.0) - c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))] - assert_true(k > 0.9) - - -def test_dpi(): - def test(xdpi, ydpi=None): im = Image.open(test_file) - im = roundtrip(im, dpi=(xdpi, ydpi or xdpi)) - return im.info.get("dpi") - assert_equal(test(72), (72, 72)) - assert_equal(test(300), (300, 300)) - assert_equal(test(100, 200), (100, 200)) - assert_equal(test(0), None) # square pixels + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "JPEG") + + def test_app(self): + # Test APP/COM reader (@PIL135) + im = Image.open(test_file) + self.assertEqual( + im.applist[0], + ("APP0", b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00")) + self.assertEqual(im.applist[1], ("COM", b"Python Imaging Library")) + self.assertEqual(len(im.applist), 2) + + def test_cmyk(self): + # Test CMYK handling. Thanks to Tim and Charlie for test data, + # Michael for getting me to look one more time. + f = "Tests/images/pil_sample_cmyk.jpg" + im = Image.open(f) + # the source image has red pixels in the upper left corner. + c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] + self.assertEqual(c, 0.0) + self.assertGreater(m, 0.8) + self.assertGreater(y, 0.8) + self.assertEqual(k, 0.0) + # the opposite corner is black + c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))] + self.assertGreater(k, 0.9) + # roundtrip, and check again + im = self.roundtrip(im) + c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] + self.assertEqual(c, 0.0) + self.assertGreater(m, 0.8) + self.assertGreater(y, 0.8) + self.assertEqual(k, 0.0) + c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))] + self.assertGreater(k, 0.9) + + def test_dpi(self): + def test(xdpi, ydpi=None): + im = Image.open(test_file) + im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) + return im.info.get("dpi") + self.assertEqual(test(72), (72, 72)) + self.assertEqual(test(300), (300, 300)) + self.assertEqual(test(100, 200), (100, 200)) + self.assertEqual(test(0), None) # square pixels + + def test_icc(self): + # Test ICC support + im1 = Image.open("Tests/images/rgb.jpg") + icc_profile = im1.info["icc_profile"] + self.assertEqual(len(icc_profile), 3144) + # Roundtrip via physical file. + f = self.tempfile("temp.jpg") + im1.save(f, icc_profile=icc_profile) + im2 = Image.open(f) + self.assertEqual(im2.info.get("icc_profile"), icc_profile) + # Roundtrip via memory buffer. + im1 = self.roundtrip(lena()) + im2 = self.roundtrip(lena(), icc_profile=icc_profile) + self.assert_image_equal(im1, im2) + self.assertFalse(im1.info.get("icc_profile")) + self.assertTrue(im2.info.get("icc_profile")) + + def test_icc_big(self): + # Make sure that the "extra" support handles large blocks + def test(n): + # The ICC APP marker can store 65519 bytes per marker, so + # using a 4-byte test code should allow us to detect out of + # order issues. + icc_profile = (b"Test"*int(n/4+1))[:n] + assert len(icc_profile) == n # sanity + im1 = self.roundtrip(lena(), icc_profile=icc_profile) + self.assertEqual(im1.info.get("icc_profile"), icc_profile or None) + test(0) + test(1) + test(3) + test(4) + test(5) + test(65533-14) # full JPEG marker block + test(65533-14+1) # full block plus one byte + test(ImageFile.MAXBLOCK) # full buffer block + test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte + test(ImageFile.MAXBLOCK*4+3) # large block + + def test_optimize(self): + im1 = self.roundtrip(lena()) + im2 = self.roundtrip(lena(), optimize=1) + self.assert_image_equal(im1, im2) + self.assertGreaterEqual(im1.bytes, im2.bytes) + + def test_optimize_large_buffer(self): + # https://github.com/python-pillow/Pillow/issues/148 + f = self.tempfile('temp.jpg') + # this requires ~ 1.5x Image.MAXBLOCK + im = Image.new("RGB", (4096, 4096), 0xff3333) + im.save(f, format="JPEG", optimize=True) + + def test_progressive(self): + im1 = self.roundtrip(lena()) + im2 = self.roundtrip(lena(), progressive=True) + self.assert_image_equal(im1, im2) + self.assertGreaterEqual(im1.bytes, im2.bytes) + + def test_progressive_large_buffer(self): + f = self.tempfile('temp.jpg') + # this requires ~ 1.5x Image.MAXBLOCK + im = Image.new("RGB", (4096, 4096), 0xff3333) + im.save(f, format="JPEG", progressive=True) + + def test_progressive_large_buffer_highest_quality(self): + f = self.tempfile('temp.jpg') + if py3: + a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3)) + else: + a = b''.join(chr(random.randint(0, 255)) for _ in range(256 * 256 * 3)) + im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1) + # this requires more bytes than pixels in the image + im.save(f, format="JPEG", progressive=True, quality=100) + + def test_large_exif(self): + # https://github.com/python-pillow/Pillow/issues/148 + f = self.tempfile('temp.jpg') + im = lena() + im.save(f, 'JPEG', quality=90, exif=b"1"*65532) + + def test_progressive_compat(self): + im1 = self.roundtrip(lena()) + im2 = self.roundtrip(lena(), progressive=1) + im3 = self.roundtrip(lena(), progression=1) # compatibility + self.assert_image_equal(im1, im2) + self.assert_image_equal(im1, im3) + self.assertFalse(im1.info.get("progressive")) + self.assertFalse(im1.info.get("progression")) + self.assertTrue(im2.info.get("progressive")) + self.assertTrue(im2.info.get("progression")) + self.assertTrue(im3.info.get("progressive")) + self.assertTrue(im3.info.get("progression")) + + def test_quality(self): + im1 = self.roundtrip(lena()) + im2 = self.roundtrip(lena(), quality=50) + self.assert_image(im1, im2.mode, im2.size) + self.assertGreaterEqual(im1.bytes, im2.bytes) + + def test_smooth(self): + im1 = self.roundtrip(lena()) + im2 = self.roundtrip(lena(), smooth=100) + self.assert_image(im1, im2.mode, im2.size) + + def test_subsampling(self): + def getsampling(im): + layer = im.layer + return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] + # experimental API + im = self.roundtrip(lena(), subsampling=-1) # default + self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + im = self.roundtrip(lena(), subsampling=0) # 4:4:4 + self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1)) + im = self.roundtrip(lena(), subsampling=1) # 4:2:2 + self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1)) + im = self.roundtrip(lena(), subsampling=2) # 4:1:1 + self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + im = self.roundtrip(lena(), subsampling=3) # default (undefined) + self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + + im = self.roundtrip(lena(), subsampling="4:4:4") + self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1)) + im = self.roundtrip(lena(), subsampling="4:2:2") + self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1)) + im = self.roundtrip(lena(), subsampling="4:1:1") + self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + + self.assertRaises( + TypeError, lambda: self.roundtrip(lena(), subsampling="1:1:1")) + + def test_exif(self): + im = Image.open("Tests/images/pil_sample_rgb.jpg") + info = im._getexif() + self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh') + + def test_quality_keep(self): + im = Image.open("Images/lena.jpg") + f = self.tempfile('temp.jpg') + im.save(f, quality='keep') + + def test_junk_jpeg_header(self): + # https://github.com/python-pillow/Pillow/issues/630 + filename = "Tests/images/junk_jpeg_header.jpg" + Image.open(filename) -def test_icc(): - # Test ICC support - im1 = Image.open("Tests/images/rgb.jpg") - icc_profile = im1.info["icc_profile"] - assert_equal(len(icc_profile), 3144) - # Roundtrip via physical file. - f = tempfile("temp.jpg") - im1.save(f, icc_profile=icc_profile) - im2 = Image.open(f) - assert_equal(im2.info.get("icc_profile"), icc_profile) - # Roundtrip via memory buffer. - im1 = roundtrip(lena()) - im2 = roundtrip(lena(), icc_profile=icc_profile) - assert_image_equal(im1, im2) - assert_false(im1.info.get("icc_profile")) - assert_true(im2.info.get("icc_profile")) - - -def test_icc_big(): - # Make sure that the "extra" support handles large blocks - def test(n): - # The ICC APP marker can store 65519 bytes per marker, so - # using a 4-byte test code should allow us to detect out of - # order issues. - icc_profile = (b"Test"*int(n/4+1))[:n] - assert len(icc_profile) == n # sanity - im1 = roundtrip(lena(), icc_profile=icc_profile) - assert_equal(im1.info.get("icc_profile"), icc_profile or None) - test(0) - test(1) - test(3) - test(4) - test(5) - test(65533-14) # full JPEG marker block - test(65533-14+1) # full block plus one byte - test(ImageFile.MAXBLOCK) # full buffer block - test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte - test(ImageFile.MAXBLOCK*4+3) # large block - - -def test_optimize(): - im1 = roundtrip(lena()) - im2 = roundtrip(lena(), optimize=1) - assert_image_equal(im1, im2) - assert_true(im1.bytes >= im2.bytes) - - -def test_optimize_large_buffer(): - # https://github.com/python-pillow/Pillow/issues/148 - f = tempfile('temp.jpg') - # this requires ~ 1.5x Image.MAXBLOCK - im = Image.new("RGB", (4096, 4096), 0xff3333) - im.save(f, format="JPEG", optimize=True) - - -def test_progressive(): - im1 = roundtrip(lena()) - im2 = roundtrip(lena(), progressive=True) - assert_image_equal(im1, im2) - assert_true(im1.bytes >= im2.bytes) - - -def test_progressive_large_buffer(): - f = tempfile('temp.jpg') - # this requires ~ 1.5x Image.MAXBLOCK - im = Image.new("RGB", (4096, 4096), 0xff3333) - im.save(f, format="JPEG", progressive=True) - - -def test_progressive_large_buffer_highest_quality(): - f = tempfile('temp.jpg') - if py3: - a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3)) - else: - a = b''.join(chr(random.randint(0, 255)) for _ in range(256 * 256 * 3)) - im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1) - # this requires more bytes than pixels in the image - im.save(f, format="JPEG", progressive=True, quality=100) - - -def test_large_exif(): - # https://github.com/python-pillow/Pillow/issues/148 - f = tempfile('temp.jpg') - im = lena() - im.save(f, 'JPEG', quality=90, exif=b"1"*65532) - - -def test_progressive_compat(): - im1 = roundtrip(lena()) - im2 = roundtrip(lena(), progressive=1) - im3 = roundtrip(lena(), progression=1) # compatibility - assert_image_equal(im1, im2) - assert_image_equal(im1, im3) - assert_false(im1.info.get("progressive")) - assert_false(im1.info.get("progression")) - assert_true(im2.info.get("progressive")) - assert_true(im2.info.get("progression")) - assert_true(im3.info.get("progressive")) - assert_true(im3.info.get("progression")) - - -def test_quality(): - im1 = roundtrip(lena()) - im2 = roundtrip(lena(), quality=50) - assert_image(im1, im2.mode, im2.size) - assert_true(im1.bytes >= im2.bytes) - - -def test_smooth(): - im1 = roundtrip(lena()) - im2 = roundtrip(lena(), smooth=100) - assert_image(im1, im2.mode, im2.size) - - -def test_subsampling(): - def getsampling(im): - layer = im.layer - return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] - # experimental API - im = roundtrip(lena(), subsampling=-1) # default - assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) - im = roundtrip(lena(), subsampling=0) # 4:4:4 - assert_equal(getsampling(im), (1, 1, 1, 1, 1, 1)) - im = roundtrip(lena(), subsampling=1) # 4:2:2 - assert_equal(getsampling(im), (2, 1, 1, 1, 1, 1)) - im = roundtrip(lena(), subsampling=2) # 4:1:1 - assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) - im = roundtrip(lena(), subsampling=3) # default (undefined) - assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) - - im = roundtrip(lena(), subsampling="4:4:4") - assert_equal(getsampling(im), (1, 1, 1, 1, 1, 1)) - im = roundtrip(lena(), subsampling="4:2:2") - assert_equal(getsampling(im), (2, 1, 1, 1, 1, 1)) - im = roundtrip(lena(), subsampling="4:1:1") - assert_equal(getsampling(im), (2, 2, 1, 1, 1, 1)) - - assert_exception(TypeError, lambda: roundtrip(lena(), subsampling="1:1:1")) - - -def test_exif(): - im = Image.open("Tests/images/pil_sample_rgb.jpg") - info = im._getexif() - assert_equal(info[305], 'Adobe Photoshop CS Macintosh') - - -def test_quality_keep(): - im = Image.open("Images/lena.jpg") - f = tempfile('temp.jpg') - assert_no_exception(lambda: im.save(f, quality='keep')) - - -def test_junk_jpeg_header(): - # https://github.com/python-pillow/Pillow/issues/630 - filename = "Tests/images/junk_jpeg_header.jpg" - assert_no_exception(lambda: Image.open(filename)) +if __name__ == '__main__': + unittest.main() # End of file diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b11e5e6ab..9111f4f8a 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,110 +1,114 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -from PIL import ImageFile +from io import BytesIO codecs = dir(Image.core) -if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - skip('JPEG 2000 support not available') - -# OpenJPEG 2.0.0 outputs this debugging message sometimes; we should -# ignore it---it doesn't represent a test failure. -ignore('Not enough memory to handle tile data') - test_card = Image.open('Tests/images/test-card.png') test_card.load() -def roundtrip(im, **options): - out = BytesIO() - im.save(out, "JPEG2000", **options) - bytes = out.tell() - out.seek(0) - im = Image.open(out) - im.bytes = bytes # for testing only - im.load() - return im +# OpenJPEG 2.0.0 outputs this debugging message sometimes; we should +# ignore it---it doesn't represent a test failure. +# 'Not enough memory to handle tile data' -# ---------------------------------------------------------------------- -def test_sanity(): - # Internal version number - assert_match(Image.core.jp2klib_version, '\d+\.\d+\.\d+$') +class TestFileJpeg2k(PillowTestCase): - im = Image.open('Tests/images/test-card-lossless.jp2') - im.load() - assert_equal(im.mode, 'RGB') - assert_equal(im.size, (640, 480)) - assert_equal(im.format, 'JPEG2000') - -# ---------------------------------------------------------------------- + def setUp(self): + if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: + self.skipTest('JPEG 2000 support not available') -# These two test pre-written JPEG 2000 files that were not written with -# PIL (they were made using Adobe Photoshop) + def roundtrip(self, im, **options): + out = BytesIO() + im.save(out, "JPEG2000", **options) + bytes = out.tell() + out.seek(0) + im = Image.open(out) + im.bytes = bytes # for testing only + im.load() + return im -def test_lossless(): - im = Image.open('Tests/images/test-card-lossless.jp2') - im.load() - im.save('/tmp/test-card.png') - assert_image_similar(im, test_card, 1.0e-3) + def test_sanity(self): + # Internal version number + self.assertRegexpMatches(Image.core.jp2klib_version, '\d+\.\d+\.\d+$') -def test_lossy_tiled(): - im = Image.open('Tests/images/test-card-lossy-tiled.jp2') - im.load() - assert_image_similar(im, test_card, 2.0) + im = Image.open('Tests/images/test-card-lossless.jp2') + im.load() + self.assertEqual(im.mode, 'RGB') + self.assertEqual(im.size, (640, 480)) + self.assertEqual(im.format, 'JPEG2000') -# ---------------------------------------------------------------------- + # These two test pre-written JPEG 2000 files that were not written with + # PIL (they were made using Adobe Photoshop) -def test_lossless_rt(): - im = roundtrip(test_card) - assert_image_equal(im, test_card) + def test_lossless(self): + im = Image.open('Tests/images/test-card-lossless.jp2') + im.load() + im.save('/tmp/test-card.png') + self.assert_image_similar(im, test_card, 1.0e-3) -def test_lossy_rt(): - im = roundtrip(test_card, quality_layers=[20]) - assert_image_similar(im, test_card, 2.0) + def test_lossy_tiled(self): + im = Image.open('Tests/images/test-card-lossy-tiled.jp2') + im.load() + self.assert_image_similar(im, test_card, 2.0) -def test_tiled_rt(): - im = roundtrip(test_card, tile_size=(128, 128)) - assert_image_equal(im, test_card) + def test_lossless_rt(self): + im = self.roundtrip(test_card) + self.assert_image_equal(im, test_card) -def test_tiled_offset_rt(): - im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), - offset=(32, 32)) - assert_image_equal(im, test_card) - -def test_irreversible_rt(): - im = roundtrip(test_card, irreversible=True, quality_layers=[20]) - assert_image_similar(im, test_card, 2.0) + def test_lossy_rt(self): + im = self.roundtrip(test_card, quality_layers=[20]) + self.assert_image_similar(im, test_card, 2.0) -def test_prog_qual_rt(): - im = roundtrip(test_card, quality_layers=[60, 40, 20], progression='LRCP') - assert_image_similar(im, test_card, 2.0) + def test_tiled_rt(self): + im = self.roundtrip(test_card, tile_size=(128, 128)) + self.assert_image_equal(im, test_card) -def test_prog_res_rt(): - im = roundtrip(test_card, num_resolutions=8, progression='RLCP') - assert_image_equal(im, test_card) + def test_tiled_offset_rt(self): + im = self.roundtrip( + test_card, tile_size=(128, 128), + tile_offset=(0, 0), offset=(32, 32)) + self.assert_image_equal(im, test_card) -# ---------------------------------------------------------------------- + def test_irreversible_rt(self): + im = self.roundtrip(test_card, irreversible=True, quality_layers=[20]) + self.assert_image_similar(im, test_card, 2.0) -def test_reduce(): - im = Image.open('Tests/images/test-card-lossless.jp2') - im.reduce = 2 - im.load() - assert_equal(im.size, (160, 120)) + def test_prog_qual_rt(self): + im = self.roundtrip( + test_card, quality_layers=[60, 40, 20], progression='LRCP') + self.assert_image_similar(im, test_card, 2.0) -def test_layers(): - out = BytesIO() - test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10], - progression='LRCP') - out.seek(0) - - im = Image.open(out) - im.layers = 1 - im.load() - assert_image_similar(im, test_card, 13) + def test_prog_res_rt(self): + im = self.roundtrip(test_card, num_resolutions=8, progression='RLCP') + self.assert_image_equal(im, test_card) - out.seek(0) - im = Image.open(out) - im.layers = 3 - im.load() - assert_image_similar(im, test_card, 0.4) + def test_reduce(self): + im = Image.open('Tests/images/test-card-lossless.jp2') + im.reduce = 2 + im.load() + self.assertEqual(im.size, (160, 120)) + + def test_layers(self): + out = BytesIO() + test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10], + progression='LRCP') + out.seek(0) + + im = Image.open(out) + im.layers = 1 + im.load() + self.assert_image_similar(im, test_card, 13) + + out.seek(0) + im = Image.open(out) + im.layers = 3 + im.load() + self.assert_image_similar(im, test_card, 0.4) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index fddff443a..1afeee488 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,300 +1,318 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena, py3 + import os from PIL import Image, TiffImagePlugin -codecs = dir(Image.core) -if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: - skip("tiff support not available") +class TestFileLibTiff(PillowTestCase): -def _assert_noerr(im): - """Helper tests that assert basic sanity about the g4 tiff reading""" - #1 bit - assert_equal(im.mode, "1") + def setUp(self): + codecs = dir(Image.core) - # Does the data actually load - assert_no_exception(lambda: im.load()) - assert_no_exception(lambda: im.getdata()) + if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: + self.skipTest("tiff support not available") - try: - assert_equal(im._compression, 'group4') - except: - print("No _compression") - print (dir(im)) + def _assert_noerr(self, im): + """Helper tests that assert basic sanity about the g4 tiff reading""" + # 1 bit + self.assertEqual(im.mode, "1") - # can we write it back out, in a different form. - out = tempfile("temp.png") - assert_no_exception(lambda: im.save(out)) + # Does the data actually load + im.load() + im.getdata() -def test_g4_tiff(): - """Test the ordinary file path load path""" + try: + self.assertEqual(im._compression, 'group4') + except: + print("No _compression") + print (dir(im)) - file = "Tests/images/lena_g4_500.tif" - im = Image.open(file) + # can we write it back out, in a different form. + out = self.tempfile("temp.png") + im.save(out) - assert_equal(im.size, (500,500)) - _assert_noerr(im) + def test_g4_tiff(self): + """Test the ordinary file path load path""" -def test_g4_large(): - file = "Tests/images/pport_g4.tif" - im = Image.open(file) - _assert_noerr(im) + file = "Tests/images/lena_g4_500.tif" + im = Image.open(file) -def test_g4_tiff_file(): - """Testing the string load path""" + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) - file = "Tests/images/lena_g4_500.tif" - with open(file,'rb') as f: - im = Image.open(f) + def test_g4_large(self): + file = "Tests/images/pport_g4.tif" + im = Image.open(file) + self._assert_noerr(im) - assert_equal(im.size, (500,500)) - _assert_noerr(im) + def test_g4_tiff_file(self): + """Testing the string load path""" -def test_g4_tiff_bytesio(): - """Testing the stringio loading code path""" - from io import BytesIO - file = "Tests/images/lena_g4_500.tif" - s = BytesIO() - with open(file,'rb') as f: - s.write(f.read()) - s.seek(0) - im = Image.open(s) + file = "Tests/images/lena_g4_500.tif" + with open(file, 'rb') as f: + im = Image.open(f) - assert_equal(im.size, (500,500)) - _assert_noerr(im) + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) -def test_g4_eq_png(): - """ Checking that we're actually getting the data that we expect""" - png = Image.open('Tests/images/lena_bw_500.png') - g4 = Image.open('Tests/images/lena_g4_500.tif') + def test_g4_tiff_bytesio(self): + """Testing the stringio loading code path""" + from io import BytesIO + file = "Tests/images/lena_g4_500.tif" + s = BytesIO() + with open(file, 'rb') as f: + s.write(f.read()) + s.seek(0) + im = Image.open(s) - assert_image_equal(g4, png) + self.assertEqual(im.size, (500, 500)) + self._assert_noerr(im) -# see https://github.com/python-pillow/Pillow/issues/279 -def test_g4_fillorder_eq_png(): - """ Checking that we're actually getting the data that we expect""" - png = Image.open('Tests/images/g4-fillorder-test.png') - g4 = Image.open('Tests/images/g4-fillorder-test.tif') + def test_g4_eq_png(self): + """ Checking that we're actually getting the data that we expect""" + png = Image.open('Tests/images/lena_bw_500.png') + g4 = Image.open('Tests/images/lena_g4_500.tif') - assert_image_equal(g4, png) + self.assert_image_equal(g4, png) -def test_g4_write(): - """Checking to see that the saved image is the same as what we wrote""" - file = "Tests/images/lena_g4_500.tif" - orig = Image.open(file) + # see https://github.com/python-pillow/Pillow/issues/279 + def test_g4_fillorder_eq_png(self): + """ Checking that we're actually getting the data that we expect""" + png = Image.open('Tests/images/g4-fillorder-test.png') + g4 = Image.open('Tests/images/g4-fillorder-test.tif') - out = tempfile("temp.tif") - rot = orig.transpose(Image.ROTATE_90) - assert_equal(rot.size,(500,500)) - rot.save(out) + self.assert_image_equal(g4, png) - reread = Image.open(out) - assert_equal(reread.size,(500,500)) - _assert_noerr(reread) - assert_image_equal(reread, rot) - assert_equal(reread.info['compression'], 'group4') + def test_g4_write(self): + """Checking to see that the saved image is the same as what we wrote""" + file = "Tests/images/lena_g4_500.tif" + orig = Image.open(file) - assert_equal(reread.info['compression'], orig.info['compression']) + out = self.tempfile("temp.tif") + rot = orig.transpose(Image.ROTATE_90) + self.assertEqual(rot.size, (500, 500)) + rot.save(out) - assert_false(orig.tobytes() == reread.tobytes()) + reread = Image.open(out) + self.assertEqual(reread.size, (500, 500)) + self._assert_noerr(reread) + self.assert_image_equal(reread, rot) + self.assertEqual(reread.info['compression'], 'group4') -def test_adobe_deflate_tiff(): - file = "Tests/images/tiff_adobe_deflate.tif" - im = Image.open(file) + self.assertEqual(reread.info['compression'], orig.info['compression']) - assert_equal(im.mode, "RGB") - assert_equal(im.size, (278, 374)) - assert_equal(im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) - assert_no_exception(lambda: im.load()) + self.assertNotEqual(orig.tobytes(), reread.tobytes()) -def test_write_metadata(): - """ Test metadata writing through libtiff """ - img = Image.open('Tests/images/lena_g4.tif') - f = tempfile('temp.tiff') + def test_adobe_deflate_tiff(self): + file = "Tests/images/tiff_adobe_deflate.tif" + im = Image.open(file) - img.save(f, tiffinfo = img.tag) + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (278, 374)) + self.assertEqual( + im.tile[0][:3], ('tiff_adobe_deflate', (0, 0, 278, 374), 0)) + im.load() - loaded = Image.open(f) + def test_write_metadata(self): + """ Test metadata writing through libtiff """ + img = Image.open('Tests/images/lena_g4.tif') + f = self.tempfile('temp.tiff') - original = img.tag.named() - reloaded = loaded.tag.named() + img.save(f, tiffinfo=img.tag) - # PhotometricInterpretation is set from SAVE_INFO, not the original image. - ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'PhotometricInterpretation'] + loaded = Image.open(f) - for tag, value in reloaded.items(): - if tag not in ignored: - if tag.endswith('Resolution'): - val = original[tag] - assert_almost_equal(val[0][0]/val[0][1], value[0][0]/value[0][1], - msg="%s didn't roundtrip" % tag) - else: - assert_equal(original[tag], value, "%s didn't roundtrip" % tag) + original = img.tag.named() + reloaded = loaded.tag.named() - for tag, value in original.items(): - if tag not in ignored: - if tag.endswith('Resolution'): - val = reloaded[tag] - assert_almost_equal(val[0][0]/val[0][1], value[0][0]/value[0][1], - msg="%s didn't roundtrip" % tag) - else: - assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) + # PhotometricInterpretation is set from SAVE_INFO, + # not the original image. + ignored = [ + 'StripByteCounts', 'RowsPerStrip', + 'PageNumber', 'PhotometricInterpretation'] + for tag, value in reloaded.items(): + if tag not in ignored: + if tag.endswith('Resolution'): + val = original[tag] + self.assert_almost_equal( + val[0][0]/val[0][1], value[0][0]/value[0][1], + msg="%s didn't roundtrip" % tag) + else: + self.assertEqual( + original[tag], value, "%s didn't roundtrip" % tag) -def test_g3_compression(): - i = Image.open('Tests/images/lena_g4_500.tif') - out = tempfile("temp.tif") - i.save(out, compression='group3') + for tag, value in original.items(): + if tag not in ignored: + if tag.endswith('Resolution'): + val = reloaded[tag] + self.assert_almost_equal( + val[0][0]/val[0][1], value[0][0]/value[0][1], + msg="%s didn't roundtrip" % tag) + else: + self.assertEqual( + value, reloaded[tag], "%s didn't roundtrip" % tag) - reread = Image.open(out) - assert_equal(reread.info['compression'], 'group3') - assert_image_equal(reread, i) + def test_g3_compression(self): + i = Image.open('Tests/images/lena_g4_500.tif') + out = self.tempfile("temp.tif") + i.save(out, compression='group3') -def test_little_endian(): - im = Image.open('Tests/images/16bit.deflate.tif') - assert_equal(im.getpixel((0,0)), 480) - assert_equal(im.mode, 'I;16') + reread = Image.open(out) + self.assertEqual(reread.info['compression'], 'group3') + self.assert_image_equal(reread, i) - b = im.tobytes() - # Bytes are in image native order (little endian) - if py3: - assert_equal(b[0], ord(b'\xe0')) - assert_equal(b[1], ord(b'\x01')) - else: - assert_equal(b[0], b'\xe0') - assert_equal(b[1], b'\x01') + def test_little_endian(self): + im = Image.open('Tests/images/16bit.deflate.tif') + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, 'I;16') + b = im.tobytes() + # Bytes are in image native order (little endian) + if py3: + self.assertEqual(b[0], ord(b'\xe0')) + self.assertEqual(b[1], ord(b'\x01')) + else: + self.assertEqual(b[0], b'\xe0') + self.assertEqual(b[1], b'\x01') - out = tempfile("temp.tif") - #out = "temp.le.tif" - im.save(out) - reread = Image.open(out) + out = self.tempfile("temp.tif") + # out = "temp.le.tif" + im.save(out) + reread = Image.open(out) - assert_equal(reread.info['compression'], im.info['compression']) - assert_equal(reread.getpixel((0,0)), 480) - # UNDONE - libtiff defaults to writing in native endian, so - # on big endian, we'll get back mode = 'I;16B' here. + self.assertEqual(reread.info['compression'], im.info['compression']) + self.assertEqual(reread.getpixel((0, 0)), 480) + # UNDONE - libtiff defaults to writing in native endian, so + # on big endian, we'll get back mode = 'I;16B' here. -def test_big_endian(): - im = Image.open('Tests/images/16bit.MM.deflate.tif') + def test_big_endian(self): + im = Image.open('Tests/images/16bit.MM.deflate.tif') - assert_equal(im.getpixel((0,0)), 480) - assert_equal(im.mode, 'I;16B') + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, 'I;16B') - b = im.tobytes() + b = im.tobytes() - # Bytes are in image native order (big endian) - if py3: - assert_equal(b[0], ord(b'\x01')) - assert_equal(b[1], ord(b'\xe0')) - else: - assert_equal(b[0], b'\x01') - assert_equal(b[1], b'\xe0') + # Bytes are in image native order (big endian) + if py3: + self.assertEqual(b[0], ord(b'\x01')) + self.assertEqual(b[1], ord(b'\xe0')) + else: + self.assertEqual(b[0], b'\x01') + self.assertEqual(b[1], b'\xe0') - out = tempfile("temp.tif") - im.save(out) - reread = Image.open(out) + out = self.tempfile("temp.tif") + im.save(out) + reread = Image.open(out) - assert_equal(reread.info['compression'], im.info['compression']) - assert_equal(reread.getpixel((0,0)), 480) + self.assertEqual(reread.info['compression'], im.info['compression']) + self.assertEqual(reread.getpixel((0, 0)), 480) -def test_g4_string_info(): - """Tests String data in info directory""" - file = "Tests/images/lena_g4_500.tif" - orig = Image.open(file) + def test_g4_string_info(self): + """Tests String data in info directory""" + file = "Tests/images/lena_g4_500.tif" + orig = Image.open(file) - out = tempfile("temp.tif") + out = self.tempfile("temp.tif") - orig.tag[269] = 'temp.tif' - orig.save(out) + orig.tag[269] = 'temp.tif' + orig.save(out) - reread = Image.open(out) - assert_equal('temp.tif', reread.tag[269]) + reread = Image.open(out) + self.assertEqual('temp.tif', reread.tag[269]) -def test_12bit_rawmode(): - """ Are we generating the same interpretation of the image as Imagemagick is? """ - TiffImagePlugin.READ_LIBTIFF = True - #Image.DEBUG = True - im = Image.open('Tests/images/12bit.cropped.tif') - im.load() - TiffImagePlugin.READ_LIBTIFF = False - # to make the target -- - # convert 12bit.cropped.tif -depth 16 tmp.tif - # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif - # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. + def test_12bit_rawmode(self): + """ Are we generating the same interpretation + of the image as Imagemagick is? """ + TiffImagePlugin.READ_LIBTIFF = True + # Image.DEBUG = True + im = Image.open('Tests/images/12bit.cropped.tif') + im.load() + TiffImagePlugin.READ_LIBTIFF = False + # to make the target -- + # convert 12bit.cropped.tif -depth 16 tmp.tif + # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif + # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, + # so we need to unshift so that the integer values are the same. - im2 = Image.open('Tests/images/12in16bit.tif') + im2 = Image.open('Tests/images/12in16bit.tif') - if Image.DEBUG: - print (im.getpixel((0,0))) - print (im.getpixel((0,1))) - print (im.getpixel((0,2))) + if Image.DEBUG: + print (im.getpixel((0, 0))) + print (im.getpixel((0, 1))) + print (im.getpixel((0, 2))) - print (im2.getpixel((0,0))) - print (im2.getpixel((0,1))) - print (im2.getpixel((0,2))) + print (im2.getpixel((0, 0))) + print (im2.getpixel((0, 1))) + print (im2.getpixel((0, 2))) - assert_image_equal(im, im2) + self.assert_image_equal(im, im2) -def test_blur(): - # test case from irc, how to do blur on b/w image and save to compressed tif. - from PIL import ImageFilter - out = tempfile('temp.tif') - im = Image.open('Tests/images/pport_g4.tif') - im = im.convert('L') + def test_blur(self): + # test case from irc, how to do blur on b/w image + # and save to compressed tif. + from PIL import ImageFilter + out = self.tempfile('temp.tif') + im = Image.open('Tests/images/pport_g4.tif') + im = im.convert('L') - im=im.filter(ImageFilter.GaussianBlur(4)) - im.save(out, compression='tiff_adobe_deflate') + im = im.filter(ImageFilter.GaussianBlur(4)) + im.save(out, compression='tiff_adobe_deflate') - im2 = Image.open(out) - im2.load() - - assert_image_equal(im, im2) - - -def test_compressions(): - im = lena('RGB') - out = tempfile('temp.tif') - - for compression in ('packbits', 'tiff_lzw'): - im.save(out, compression=compression) im2 = Image.open(out) - assert_image_equal(im, im2) + im2.load() - im.save(out, compression='jpeg') - im2 = Image.open(out) - assert_image_similar(im, im2, 30) + self.assert_image_equal(im, im2) + + def test_compressions(self): + im = lena('RGB') + out = self.tempfile('temp.tif') + + for compression in ('packbits', 'tiff_lzw'): + im.save(out, compression=compression) + im2 = Image.open(out) + self.assert_image_equal(im, im2) + + im.save(out, compression='jpeg') + im2 = Image.open(out) + self.assert_image_similar(im, im2, 30) + + def test_cmyk_save(self): + im = lena('CMYK') + out = self.tempfile('temp.tif') + + im.save(out, compression='tiff_adobe_deflate') + im2 = Image.open(out) + self.assert_image_equal(im, im2) + + def xtest_bw_compression_wRGB(self): + """ This test passes, but when running all tests causes a failure due + to output on stderr from the error thrown by libtiff. We need to + capture that but not now""" + + im = lena('RGB') + out = self.tempfile('temp.tif') + + self.assertRaises( + IOError, lambda: im.save(out, compression='tiff_ccitt')) + self.assertRaises(IOError, lambda: im.save(out, compression='group3')) + self.assertRaises(IOError, lambda: im.save(out, compression='group4')) + + def test_fp_leak(self): + im = Image.open("Tests/images/lena_g4_500.tif") + fn = im.fp.fileno() + + os.fstat(fn) + im.load() # this should close it. + self.assertRaises(OSError, lambda: os.fstat(fn)) + im = None # this should force even more closed. + self.assertRaises(OSError, lambda: os.fstat(fn)) + self.assertRaises(OSError, lambda: os.close(fn)) -def test_cmyk_save(): - im = lena('CMYK') - out = tempfile('temp.tif') +if __name__ == '__main__': + unittest.main() - im.save(out, compression='tiff_adobe_deflate') - im2 = Image.open(out) - assert_image_equal(im, im2) - -def xtest_bw_compression_wRGB(): - """ This test passes, but when running all tests causes a failure due to - output on stderr from the error thrown by libtiff. We need to capture that - but not now""" - - im = lena('RGB') - out = tempfile('temp.tif') - - assert_exception(IOError, lambda: im.save(out, compression='tiff_ccitt')) - assert_exception(IOError, lambda: im.save(out, compression='group3')) - assert_exception(IOError, lambda: im.save(out, compression='group4')) - -def test_fp_leak(): - im = Image.open("Tests/images/lena_g4_500.tif") - fn = im.fp.fileno() - - assert_no_exception(lambda: os.fstat(fn)) - im.load() # this should close it. - assert_exception(OSError, lambda: os.fstat(fn)) - im = None # this should force even more closed. - assert_exception(OSError, lambda: os.fstat(fn)) - assert_exception(OSError, lambda: os.close(fn)) +# End of file diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 2ad71d6e6..acc2390c9 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -1,52 +1,56 @@ -from tester import * +from helper import unittest, tearDownModule from PIL import Image -from test_file_libtiff import _assert_noerr - -codecs = dir(Image.core) - -if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: - skip("tiff support not available") - -""" The small lena image was failing on open in the libtiff - decoder because the file pointer was set to the wrong place - by a spurious seek. It wasn't failing with the byteio method. - - It was fixed by forcing an lseek to the beginning of the - file just before reading in libtiff. These tests remain - to ensure that it stays fixed. """ +from test_file_libtiff import TestFileLibTiff -def test_g4_lena_file(): - """Testing the open file load path""" +class TestFileLibTiffSmall(TestFileLibTiff): - file = "Tests/images/lena_g4.tif" - with open(file,'rb') as f: - im = Image.open(f) + # Inherits TestFileLibTiff's setUp() and self._assert_noerr() - assert_equal(im.size, (128,128)) - _assert_noerr(im) + """ The small lena image was failing on open in the libtiff + decoder because the file pointer was set to the wrong place + by a spurious seek. It wasn't failing with the byteio method. -def test_g4_lena_bytesio(): - """Testing the bytesio loading code path""" - from io import BytesIO - file = "Tests/images/lena_g4.tif" - s = BytesIO() - with open(file,'rb') as f: - s.write(f.read()) - s.seek(0) - im = Image.open(s) + It was fixed by forcing an lseek to the beginning of the + file just before reading in libtiff. These tests remain + to ensure that it stays fixed. """ - assert_equal(im.size, (128,128)) - _assert_noerr(im) + def test_g4_lena_file(self): + """Testing the open file load path""" -def test_g4_lena(): - """The 128x128 lena image fails for some reason. Investigating""" + file = "Tests/images/lena_g4.tif" + with open(file, 'rb') as f: + im = Image.open(f) - file = "Tests/images/lena_g4.tif" - im = Image.open(file) + self.assertEqual(im.size, (128, 128)) + self._assert_noerr(im) - assert_equal(im.size, (128,128)) - _assert_noerr(im) + def test_g4_lena_bytesio(self): + """Testing the bytesio loading code path""" + from io import BytesIO + file = "Tests/images/lena_g4.tif" + s = BytesIO() + with open(file, 'rb') as f: + s.write(f.read()) + s.seek(0) + im = Image.open(s) + self.assertEqual(im.size, (128, 128)) + self._assert_noerr(im) + + def test_g4_lena(self): + """The 128x128 lena image fails for some reason. Investigating""" + + file = "Tests/images/lena_g4.tif" + im = Image.open(file) + + self.assertEqual(im.size, (128, 128)) + self._assert_noerr(im) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 7ed200e43..2444879d1 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,15 +1,24 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -def test_sanity(): - file = tempfile("temp.msp") +class TestFileMsp(PillowTestCase): - lena("1").save(file) + def test_sanity(self): - im = Image.open(file) - im.load() - assert_equal(im.mode, "1") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "MSP") + file = self.tempfile("temp.msp") + + lena("1").save(file) + + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "1") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "MSP") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index cb785ec54..d0800e203 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,40 +1,47 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -def _roundtrip(im): - f = tempfile("temp.pcx") - im.save(f) - im2 = Image.open(f) +class TestFilePcx(PillowTestCase): - assert_equal(im2.mode, im.mode) - assert_equal(im2.size, im.size) - assert_equal(im2.format, "PCX") - assert_image_equal(im2, im) - -def test_sanity(): - for mode in ('1', 'L', 'P', 'RGB'): - _roundtrip(lena(mode)) + def _roundtrip(self, im): + f = self.tempfile("temp.pcx") + im.save(f) + im2 = Image.open(f) -def test_odd(): - # see issue #523, odd sized images should have a stride that's even. - # not that imagemagick or gimp write pcx that way. - # we were not handling properly. - for mode in ('1', 'L', 'P', 'RGB'): - # larger, odd sized images are better here to ensure that - # we handle interrupted scan lines properly. - _roundtrip(lena(mode).resize((511,511))) - + self.assertEqual(im2.mode, im.mode) + self.assertEqual(im2.size, im.size) + self.assertEqual(im2.format, "PCX") + self.assert_image_equal(im2, im) -def test_pil184(): - # Check reading of files where xmin/xmax is not zero. + def test_sanity(self): + for mode in ('1', 'L', 'P', 'RGB'): + self._roundtrip(lena(mode)) - file = "Tests/images/pil184.pcx" - im = Image.open(file) + def test_odd(self): + # see issue #523, odd sized images should have a stride that's even. + # not that imagemagick or gimp write pcx that way. + # we were not handling properly. + for mode in ('1', 'L', 'P', 'RGB'): + # larger, odd sized images are better here to ensure that + # we handle interrupted scan lines properly. + self._roundtrip(lena(mode).resize((511, 511))) - assert_equal(im.size, (447, 144)) - assert_equal(im.tile[0][1], (0, 0, 447, 144)) + def test_pil184(self): + # Check reading of files where xmin/xmax is not zero. - # Make sure all pixels are either 0 or 255. - assert_equal(im.histogram()[0] + im.histogram()[255], 447*144) + file = "Tests/images/pil184.pcx" + im = Image.open(file) + + self.assertEqual(im.size, (447, 144)) + self.assertEqual(im.tile[0][1], (0, 0, 447, 144)) + + # Make sure all pixels are either 0 or 255. + self.assertEqual(im.histogram()[0] + im.histogram()[255], 447*144) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index e99f22db1..089168393 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -1,58 +1,59 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena + import os.path -def helper_save_as_pdf(mode): - # Arrange - im = lena(mode) - outfile = tempfile("temp_" + mode + ".pdf") +class TestFilePdf(PillowTestCase): - # Act - im.save(outfile) + def helper_save_as_pdf(self, mode): + # Arrange + im = lena(mode) + outfile = self.tempfile("temp_" + mode + ".pdf") - # Assert - assert_true(os.path.isfile(outfile)) - assert_greater(os.path.getsize(outfile), 0) + # Act + im.save(outfile) + + # Assert + self.assertTrue(os.path.isfile(outfile)) + self.assertGreater(os.path.getsize(outfile), 0) + + def test_monochrome(self): + # Arrange + mode = "1" + + # Act / Assert + self.helper_save_as_pdf(mode) + + def test_greyscale(self): + # Arrange + mode = "L" + + # Act / Assert + self.helper_save_as_pdf(mode) + + def test_rgb(self): + # Arrange + mode = "RGB" + + # Act / Assert + self.helper_save_as_pdf(mode) + + def test_p_mode(self): + # Arrange + mode = "P" + + # Act / Assert + self.helper_save_as_pdf(mode) + + def test_cmyk_mode(self): + # Arrange + mode = "CMYK" + + # Act / Assert + self.helper_save_as_pdf(mode) -def test_monochrome(): - # Arrange - mode = "1" - - # Act / Assert - helper_save_as_pdf(mode) - - -def test_greyscale(): - # Arrange - mode = "L" - - # Act / Assert - helper_save_as_pdf(mode) - - -def test_rgb(): - # Arrange - mode = "RGB" - - # Act / Assert - helper_save_as_pdf(mode) - - -def test_p_mode(): - # Arrange - mode = "P" - - # Act / Assert - helper_save_as_pdf(mode) - - -def test_cmyk_mode(): - # Arrange - mode = "CMYK" - - # Act / Assert - helper_save_as_pdf(mode) - +if __name__ == '__main__': + unittest.main() # End of file diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0b0ad3ca7..6da61be6b 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,4 +1,6 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena + +from io import BytesIO from PIL import Image from PIL import PngImagePlugin @@ -6,9 +8,6 @@ import zlib codecs = dir(Image.core) -if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - skip("zip/deflate support not available") - # sample png stream file = "Images/lena.png" @@ -18,6 +17,7 @@ data = open(file, "rb").read() MAGIC = PngImagePlugin._MAGIC + def chunk(cid, *data): file = BytesIO() PngImagePlugin.putchunk(*(file, cid) + data) @@ -32,256 +32,268 @@ IEND = chunk(b"IEND") HEAD = MAGIC + IHDR TAIL = IDAT + IEND + def load(data): return Image.open(BytesIO(data)) + def roundtrip(im, **options): out = BytesIO() im.save(out, "PNG", **options) out.seek(0) return Image.open(out) -# -------------------------------------------------------------------- -def test_sanity(): +class TestFilePng(PillowTestCase): - # internal version number - assert_match(Image.core.zlib_version, "\d+\.\d+\.\d+(\.\d+)?$") + def setUp(self): + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + self.skipTest("zip/deflate support not available") - file = tempfile("temp.png") + def test_sanity(self): - lena("RGB").save(file) + # internal version number + self.assertRegexpMatches( + Image.core.zlib_version, "\d+\.\d+\.\d+(\.\d+)?$") - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "PNG") + file = self.tempfile("temp.png") - lena("1").save(file) - im = Image.open(file) + lena("RGB").save(file) - lena("L").save(file) - im = Image.open(file) + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PNG") - lena("P").save(file) - im = Image.open(file) + lena("1").save(file) + im = Image.open(file) - lena("RGB").save(file) - im = Image.open(file) + lena("L").save(file) + im = Image.open(file) - lena("I").save(file) - im = Image.open(file) + lena("P").save(file) + im = Image.open(file) -# -------------------------------------------------------------------- + lena("RGB").save(file) + im = Image.open(file) -def test_broken(): - # Check reading of totally broken files. In this case, the test - # file was checked into Subversion as a text file. + lena("I").save(file) + im = Image.open(file) - file = "Tests/images/broken.png" - assert_exception(IOError, lambda: Image.open(file)) + def test_broken(self): + # Check reading of totally broken files. In this case, the test + # file was checked into Subversion as a text file. -def test_bad_text(): - # Make sure PIL can read malformed tEXt chunks (@PIL152) + file = "Tests/images/broken.png" + self.assertRaises(IOError, lambda: Image.open(file)) - im = load(HEAD + chunk(b'tEXt') + TAIL) - assert_equal(im.info, {}) + def test_bad_text(self): + # Make sure PIL can read malformed tEXt chunks (@PIL152) - im = load(HEAD + chunk(b'tEXt', b'spam') + TAIL) - assert_equal(im.info, {'spam': ''}) + im = load(HEAD + chunk(b'tEXt') + TAIL) + self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'tEXt', b'spam\0') + TAIL) - assert_equal(im.info, {'spam': ''}) + im = load(HEAD + chunk(b'tEXt', b'spam') + TAIL) + self.assertEqual(im.info, {'spam': ''}) - im = load(HEAD + chunk(b'tEXt', b'spam\0egg') + TAIL) - assert_equal(im.info, {'spam': 'egg'}) + im = load(HEAD + chunk(b'tEXt', b'spam\0') + TAIL) + self.assertEqual(im.info, {'spam': ''}) - im = load(HEAD + chunk(b'tEXt', b'spam\0egg\0') + TAIL) - assert_equal(im.info, {'spam': 'egg\x00'}) + im = load(HEAD + chunk(b'tEXt', b'spam\0egg') + TAIL) + self.assertEqual(im.info, {'spam': 'egg'}) -def test_bad_ztxt(): - # Test reading malformed zTXt chunks (python-pillow/Pillow#318) + im = load(HEAD + chunk(b'tEXt', b'spam\0egg\0') + TAIL) + self.assertEqual(im.info, {'spam': 'egg\x00'}) - im = load(HEAD + chunk(b'zTXt') + TAIL) - assert_equal(im.info, {}) + def test_bad_ztxt(self): + # Test reading malformed zTXt chunks (python-pillow/Pillow#318) - im = load(HEAD + chunk(b'zTXt', b'spam') + TAIL) - assert_equal(im.info, {'spam': ''}) + im = load(HEAD + chunk(b'zTXt') + TAIL) + self.assertEqual(im.info, {}) - im = load(HEAD + chunk(b'zTXt', b'spam\0') + TAIL) - assert_equal(im.info, {'spam': ''}) + im = load(HEAD + chunk(b'zTXt', b'spam') + TAIL) + self.assertEqual(im.info, {'spam': ''}) - im = load(HEAD + chunk(b'zTXt', b'spam\0\0') + TAIL) - assert_equal(im.info, {'spam': ''}) + im = load(HEAD + chunk(b'zTXt', b'spam\0') + TAIL) + self.assertEqual(im.info, {'spam': ''}) - im = load(HEAD + chunk(b'zTXt', b'spam\0\0' + zlib.compress(b'egg')[:1]) + TAIL) - assert_equal(im.info, {'spam': ''}) + im = load(HEAD + chunk(b'zTXt', b'spam\0\0') + TAIL) + self.assertEqual(im.info, {'spam': ''}) - im = load(HEAD + chunk(b'zTXt', b'spam\0\0' + zlib.compress(b'egg')) + TAIL) - assert_equal(im.info, {'spam': 'egg'}) + im = load(HEAD + chunk( + b'zTXt', b'spam\0\0' + zlib.compress(b'egg')[:1]) + TAIL) + self.assertEqual(im.info, {'spam': ''}) -def test_interlace(): + im = load( + HEAD + chunk(b'zTXt', b'spam\0\0' + zlib.compress(b'egg')) + TAIL) + self.assertEqual(im.info, {'spam': 'egg'}) - file = "Tests/images/pil123p.png" - im = Image.open(file) + def test_interlace(self): - assert_image(im, "P", (162, 150)) - assert_true(im.info.get("interlace")) + file = "Tests/images/pil123p.png" + im = Image.open(file) - assert_no_exception(lambda: im.load()) + self.assert_image(im, "P", (162, 150)) + self.assertTrue(im.info.get("interlace")) - file = "Tests/images/pil123rgba.png" - im = Image.open(file) + im.load() - assert_image(im, "RGBA", (162, 150)) - assert_true(im.info.get("interlace")) + file = "Tests/images/pil123rgba.png" + im = Image.open(file) - assert_no_exception(lambda: im.load()) + self.assert_image(im, "RGBA", (162, 150)) + self.assertTrue(im.info.get("interlace")) -def test_load_transparent_p(): - file = "Tests/images/pil123p.png" - im = Image.open(file) + im.load() - assert_image(im, "P", (162, 150)) - im = im.convert("RGBA") - assert_image(im, "RGBA", (162, 150)) + def test_load_transparent_p(self): + file = "Tests/images/pil123p.png" + im = Image.open(file) - # image has 124 uniqe qlpha values - assert_equal(len(im.split()[3].getcolors()), 124) + self.assert_image(im, "P", (162, 150)) + im = im.convert("RGBA") + self.assert_image(im, "RGBA", (162, 150)) -def test_load_transparent_rgb(): - file = "Tests/images/rgb_trns.png" - im = Image.open(file) + # image has 124 uniqe qlpha values + self.assertEqual(len(im.split()[3].getcolors()), 124) - assert_image(im, "RGB", (64, 64)) - im = im.convert("RGBA") - assert_image(im, "RGBA", (64, 64)) + def test_load_transparent_rgb(self): + file = "Tests/images/rgb_trns.png" + im = Image.open(file) - # image has 876 transparent pixels - assert_equal(im.split()[3].getcolors()[0][0], 876) + self.assert_image(im, "RGB", (64, 64)) + im = im.convert("RGBA") + self.assert_image(im, "RGBA", (64, 64)) -def test_save_p_transparent_palette(): - in_file = "Tests/images/pil123p.png" - im = Image.open(in_file) + # image has 876 transparent pixels + self.assertEqual(im.split()[3].getcolors()[0][0], 876) - file = tempfile("temp.png") - assert_no_exception(lambda: im.save(file)) + def test_save_p_transparent_palette(self): + in_file = "Tests/images/pil123p.png" + im = Image.open(in_file) -def test_save_p_single_transparency(): - in_file = "Tests/images/p_trns_single.png" - im = Image.open(in_file) + file = self.tempfile("temp.png") + im.save(file) - file = tempfile("temp.png") - assert_no_exception(lambda: im.save(file)) + def test_save_p_single_transparency(self): + in_file = "Tests/images/p_trns_single.png" + im = Image.open(in_file) -def test_save_l_transparency(): - in_file = "Tests/images/l_trns.png" - im = Image.open(in_file) + file = self.tempfile("temp.png") + im.save(file) - file = tempfile("temp.png") - assert_no_exception(lambda: im.save(file)) + def test_save_l_transparency(self): + in_file = "Tests/images/l_trns.png" + im = Image.open(in_file) - # There are 559 transparent pixels. - im = im.convert('RGBA') - assert_equal(im.split()[3].getcolors()[0][0], 559) + file = self.tempfile("temp.png") + im.save(file) -def test_save_rgb_single_transparency(): - in_file = "Tests/images/caption_6_33_22.png" - im = Image.open(in_file) + # There are 559 transparent pixels. + im = im.convert('RGBA') + self.assertEqual(im.split()[3].getcolors()[0][0], 559) - file = tempfile("temp.png") - assert_no_exception(lambda: im.save(file)) + def test_save_rgb_single_transparency(self): + in_file = "Tests/images/caption_6_33_22.png" + im = Image.open(in_file) -def test_load_verify(): - # Check open/load/verify exception (@PIL150) + file = self.tempfile("temp.png") + im.save(file) - im = Image.open("Images/lena.png") - assert_no_exception(lambda: im.verify()) + def test_load_verify(self): + # Check open/load/verify exception (@PIL150) - im = Image.open("Images/lena.png") - im.load() - assert_exception(RuntimeError, lambda: im.verify()) + im = Image.open("Images/lena.png") + im.verify() -def test_roundtrip_dpi(): - # Check dpi roundtripping + im = Image.open("Images/lena.png") + im.load() + self.assertRaises(RuntimeError, lambda: im.verify()) - im = Image.open(file) + def test_roundtrip_dpi(self): + # Check dpi roundtripping - im = roundtrip(im, dpi=(100, 100)) - assert_equal(im.info["dpi"], (100, 100)) + im = Image.open(file) -def test_roundtrip_text(): - # Check text roundtripping + im = roundtrip(im, dpi=(100, 100)) + self.assertEqual(im.info["dpi"], (100, 100)) - im = Image.open(file) + def test_roundtrip_text(self): + # Check text roundtripping - info = PngImagePlugin.PngInfo() - info.add_text("TXT", "VALUE") - info.add_text("ZIP", "VALUE", 1) + im = Image.open(file) - im = roundtrip(im, pnginfo=info) - assert_equal(im.info, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) - assert_equal(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) + info = PngImagePlugin.PngInfo() + info.add_text("TXT", "VALUE") + info.add_text("ZIP", "VALUE", 1) -def test_scary(): - # Check reading of evil PNG file. For information, see: - # http://scary.beasts.org/security/CESA-2004-001.txt - # The first byte is removed from pngtest_bad.png - # to avoid classification as malware. + im = roundtrip(im, pnginfo=info) + self.assertEqual(im.info, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) + self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) - with open("Tests/images/pngtest_bad.png.bin", 'rb') as fd: - data = b'\x89' + fd.read() + def test_scary(self): + # Check reading of evil PNG file. For information, see: + # http://scary.beasts.org/security/CESA-2004-001.txt + # The first byte is removed from pngtest_bad.png + # to avoid classification as malware. - pngfile = BytesIO(data) - assert_exception(IOError, lambda: Image.open(pngfile)) + with open("Tests/images/pngtest_bad.png.bin", 'rb') as fd: + data = b'\x89' + fd.read() -def test_trns_rgb(): - # Check writing and reading of tRNS chunks for RGB images. - # Independent file sample provided by Sebastian Spaeth. + pngfile = BytesIO(data) + self.assertRaises(IOError, lambda: Image.open(pngfile)) - file = "Tests/images/caption_6_33_22.png" - im = Image.open(file) - assert_equal(im.info["transparency"], (248, 248, 248)) + def test_trns_rgb(self): + # Check writing and reading of tRNS chunks for RGB images. + # Independent file sample provided by Sebastian Spaeth. - # check saving transparency by default - im = roundtrip(im) - assert_equal(im.info["transparency"], (248, 248, 248)) + file = "Tests/images/caption_6_33_22.png" + im = Image.open(file) + self.assertEqual(im.info["transparency"], (248, 248, 248)) - im = roundtrip(im, transparency=(0, 1, 2)) - assert_equal(im.info["transparency"], (0, 1, 2)) + # check saving transparency by default + im = roundtrip(im) + self.assertEqual(im.info["transparency"], (248, 248, 248)) -def test_trns_p(): - # Check writing a transparency of 0, issue #528 - im = lena('P') - im.info['transparency']=0 + im = roundtrip(im, transparency=(0, 1, 2)) + self.assertEqual(im.info["transparency"], (0, 1, 2)) - f = tempfile("temp.png") - im.save(f) + def test_trns_p(self): + # Check writing a transparency of 0, issue #528 + im = lena('P') + im.info['transparency'] = 0 - im2 = Image.open(f) - assert_true('transparency' in im2.info) + f = self.tempfile("temp.png") + im.save(f) - assert_image_equal(im2.convert('RGBA'), im.convert('RGBA')) + im2 = Image.open(f) + self.assertIn('transparency', im2.info) + self.assert_image_equal(im2.convert('RGBA'), im.convert('RGBA')) -def test_save_icc_profile_none(): - # check saving files with an ICC profile set to None (omit profile) - in_file = "Tests/images/icc_profile_none.png" - im = Image.open(in_file) - assert_equal(im.info['icc_profile'], None) + def test_save_icc_profile_none(self): + # check saving files with an ICC profile set to None (omit profile) + in_file = "Tests/images/icc_profile_none.png" + im = Image.open(in_file) + self.assertEqual(im.info['icc_profile'], None) - im = roundtrip(im) - assert_false('icc_profile' in im.info) + im = roundtrip(im) + self.assertNotIn('icc_profile', im.info) -def test_roundtrip_icc_profile(): - # check that we can roundtrip the icc profile - im = lena('RGB') + def test_roundtrip_icc_profile(self): + # check that we can roundtrip the icc profile + im = lena('RGB') - jpeg_image = Image.open('Tests/images/flower2.jpg') - expected_icc = jpeg_image.info['icc_profile'] + jpeg_image = Image.open('Tests/images/flower2.jpg') + expected_icc = jpeg_image.info['icc_profile'] - im.info['icc_profile'] = expected_icc - im = roundtrip(im) - assert_equal(im.info['icc_profile'], expected_icc) + im.info['icc_profile'] = expected_icc + im = roundtrip(im) + self.assertEqual(im.info['icc_profile'], expected_icc) + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 34136a83c..9d5dd806a 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -6,31 +6,37 @@ from PIL import Image file = "Images/lena.ppm" data = open(file, "rb").read() -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "PPM") -def test_16bit_pgm(): - im = Image.open('Tests/images/16_bit_binary.pgm') - im.load() - assert_equal(im.mode, 'I') - assert_equal(im.size, (20,100)) +class TestFilePpm(PillowTestCase): - tgt = Image.open('Tests/images/16_bit_binary_pgm.png') - assert_image_equal(im, tgt) + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PPM") + + def test_16bit_pgm(self): + im = Image.open('Tests/images/16_bit_binary.pgm') + im.load() + self.assertEqual(im.mode, 'I') + self.assertEqual(im.size, (20, 100)) + + tgt = Image.open('Tests/images/16_bit_binary_pgm.png') + self.assert_image_equal(im, tgt) + + def test_16bit_pgm_write(self): + im = Image.open('Tests/images/16_bit_binary.pgm') + im.load() + + f = self.tempfile('temp.pgm') + im.save(f, 'PPM') + + reloaded = Image.open(f) + self.assert_image_equal(im, reloaded) -def test_16bit_pgm_write(): - im = Image.open('Tests/images/16_bit_binary.pgm') - im.load() - - f = tempfile('temp.pgm') - assert_no_exception(lambda: im.save(f, 'PPM')) - - reloaded = Image.open(f) - assert_image_equal(im, reloaded) - +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index ef2d40594..e7f86fd98 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -6,9 +6,18 @@ from PIL import Image file = "Images/lena.psd" data = open(file, "rb").read() -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "PSD") + +class TestImagePsd(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PSD") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index d93724492..e0731ca8c 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import SpiderImagePlugin @@ -6,31 +6,34 @@ from PIL import SpiderImagePlugin test_file = "Tests/images/lena.spider" -def test_sanity(): - im = Image.open(test_file) - im.load() - assert_equal(im.mode, "F") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "SPIDER") +class TestImageSpider(PillowTestCase): + + def test_sanity(self): + im = Image.open(test_file) + im.load() + self.assertEqual(im.mode, "F") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "SPIDER") + + def test_save(self): + # Arrange + temp = self.tempfile('temp.spider') + im = lena() + + # Act + im.save(temp, "SPIDER") + + # Assert + im2 = Image.open(temp) + self.assertEqual(im2.mode, "F") + self.assertEqual(im2.size, (128, 128)) + self.assertEqual(im2.format, "SPIDER") + + def test_isSpiderImage(self): + self.assertTrue(SpiderImagePlugin.isSpiderImage(test_file)) -def test_save(): - # Arrange - temp = tempfile('temp.spider') - im = lena() - - # Act - im.save(temp, "SPIDER") - - # Assert - im2 = Image.open(temp) - assert_equal(im2.mode, "F") - assert_equal(im2.size, (128, 128)) - assert_equal(im2.format, "SPIDER") - - -def test_isSpiderImage(): - assert_true(SpiderImagePlugin.isSpiderImage(test_file)) - +if __name__ == '__main__': + unittest.main() # End of file diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index fa33d3802..36a625add 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,28 +1,38 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image, TarIO codecs = dir(Image.core) -if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: - skip("neither jpeg nor zip support not available") # sample ppm stream tarfile = "Images/lena.tar" -def test_sanity(): - if "zip_decoder" in codecs: - tar = TarIO.TarIO(tarfile, 'lena.png') - im = Image.open(tar) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "PNG") - if "jpeg_decoder" in codecs: - tar = TarIO.TarIO(tarfile, 'lena.jpg') - im = Image.open(tar) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "JPEG") +class TestFileTar(PillowTestCase): + def setUp(self): + if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: + self.skipTest("neither jpeg nor zip support not available") + + def test_sanity(self): + if "zip_decoder" in codecs: + tar = TarIO.TarIO(tarfile, 'lena.png') + im = Image.open(tar) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "PNG") + + if "jpeg_decoder" in codecs: + tar = TarIO.TarIO(tarfile, 'lena.jpg') + im = Image.open(tar) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "JPEG") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 804ae04e4..ed3d1e9cd 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,141 +1,148 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena, py3 from PIL import Image -def test_sanity(): - file = tempfile("temp.tif") +class TestFileTiff(PillowTestCase): - lena("RGB").save(file) + def test_sanity(self): - im = Image.open(file) - im.load() - assert_equal(im.mode, "RGB") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "TIFF") + file = self.tempfile("temp.tif") - lena("1").save(file) - im = Image.open(file) + lena("RGB").save(file) - lena("L").save(file) - im = Image.open(file) + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "TIFF") - lena("P").save(file) - im = Image.open(file) + lena("1").save(file) + im = Image.open(file) - lena("RGB").save(file) - im = Image.open(file) + lena("L").save(file) + im = Image.open(file) - lena("I").save(file) - im = Image.open(file) + lena("P").save(file) + im = Image.open(file) -def test_mac_tiff(): - # Read RGBa images from Mac OS X [@PIL136] + lena("RGB").save(file) + im = Image.open(file) - file = "Tests/images/pil136.tiff" - im = Image.open(file) + lena("I").save(file) + im = Image.open(file) - assert_equal(im.mode, "RGBA") - assert_equal(im.size, (55, 43)) - assert_equal(im.tile, [('raw', (0, 0, 55, 43), 8, ('RGBa', 0, 1))]) - assert_no_exception(lambda: im.load()) + def test_mac_tiff(self): + # Read RGBa images from Mac OS X [@PIL136] -def test_gimp_tiff(): - # Read TIFF JPEG images from GIMP [@PIL168] + file = "Tests/images/pil136.tiff" + im = Image.open(file) - codecs = dir(Image.core) - if "jpeg_decoder" not in codecs: - skip("jpeg support not available") + self.assertEqual(im.mode, "RGBA") + self.assertEqual(im.size, (55, 43)) + self.assertEqual(im.tile, [('raw', (0, 0, 55, 43), 8, ('RGBa', 0, 1))]) + im.load() - file = "Tests/images/pil168.tif" - im = Image.open(file) + def test_gimp_tiff(self): + # Read TIFF JPEG images from GIMP [@PIL168] - assert_equal(im.mode, "RGB") - assert_equal(im.size, (256, 256)) - assert_equal(im.tile, [ - ('jpeg', (0, 0, 256, 64), 8, ('RGB', '')), - ('jpeg', (0, 64, 256, 128), 1215, ('RGB', '')), - ('jpeg', (0, 128, 256, 192), 2550, ('RGB', '')), - ('jpeg', (0, 192, 256, 256), 3890, ('RGB', '')), - ]) - assert_no_exception(lambda: im.load()) + codecs = dir(Image.core) + if "jpeg_decoder" not in codecs: + self.skipTest("jpeg support not available") -def test_xyres_tiff(): - from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION - file = "Tests/images/pil168.tif" - im = Image.open(file) - assert isinstance(im.tag.tags[X_RESOLUTION][0], tuple) - assert isinstance(im.tag.tags[Y_RESOLUTION][0], tuple) - #Try to read a file where X,Y_RESOLUTION are ints - im.tag.tags[X_RESOLUTION] = (72,) - im.tag.tags[Y_RESOLUTION] = (72,) - im._setup() - assert_equal(im.info['dpi'], (72., 72.)) + file = "Tests/images/pil168.tif" + im = Image.open(file) + + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (256, 256)) + self.assertEqual( + im.tile, [ + ('jpeg', (0, 0, 256, 64), 8, ('RGB', '')), + ('jpeg', (0, 64, 256, 128), 1215, ('RGB', '')), + ('jpeg', (0, 128, 256, 192), 2550, ('RGB', '')), + ('jpeg', (0, 192, 256, 256), 3890, ('RGB', '')), + ]) + im.load() + + def test_xyres_tiff(self): + from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION + file = "Tests/images/pil168.tif" + im = Image.open(file) + assert isinstance(im.tag.tags[X_RESOLUTION][0], tuple) + assert isinstance(im.tag.tags[Y_RESOLUTION][0], tuple) + # Try to read a file where X,Y_RESOLUTION are ints + im.tag.tags[X_RESOLUTION] = (72,) + im.tag.tags[Y_RESOLUTION] = (72,) + im._setup() + self.assertEqual(im.info['dpi'], (72., 72.)) + + def test_little_endian(self): + im = Image.open('Tests/images/16bit.cropped.tif') + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, 'I;16') + + b = im.tobytes() + # Bytes are in image native order (little endian) + if py3: + self.assertEqual(b[0], ord(b'\xe0')) + self.assertEqual(b[1], ord(b'\x01')) + else: + self.assertEqual(b[0], b'\xe0') + self.assertEqual(b[1], b'\x01') + + def test_big_endian(self): + im = Image.open('Tests/images/16bit.MM.cropped.tif') + self.assertEqual(im.getpixel((0, 0)), 480) + self.assertEqual(im.mode, 'I;16B') + + b = im.tobytes() + + # Bytes are in image native order (big endian) + if py3: + self.assertEqual(b[0], ord(b'\x01')) + self.assertEqual(b[1], ord(b'\xe0')) + else: + self.assertEqual(b[0], b'\x01') + self.assertEqual(b[1], b'\xe0') + + def test_12bit_rawmode(self): + """ Are we generating the same interpretation + of the image as Imagemagick is? """ + + # Image.DEBUG = True + im = Image.open('Tests/images/12bit.cropped.tif') + + # to make the target -- + # convert 12bit.cropped.tif -depth 16 tmp.tif + # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif + # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, + # so we need to unshift so that the integer values are the same. + + im2 = Image.open('Tests/images/12in16bit.tif') + + if Image.DEBUG: + print (im.getpixel((0, 0))) + print (im.getpixel((0, 1))) + print (im.getpixel((0, 2))) + + print (im2.getpixel((0, 0))) + print (im2.getpixel((0, 1))) + print (im2.getpixel((0, 2))) + + self.assert_image_equal(im, im2) + + def test_32bit_float(self): + # Issue 614, specific 32 bit float format + path = 'Tests/images/10ct_32bit_128.tiff' + im = Image.open(path) + im.load() + + self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343) + self.assertEqual( + im.getextrema(), (-3.140936851501465, 3.140684127807617)) -def test_little_endian(): - im = Image.open('Tests/images/16bit.cropped.tif') - assert_equal(im.getpixel((0,0)), 480) - assert_equal(im.mode, 'I;16') +if __name__ == '__main__': + unittest.main() - b = im.tobytes() - # Bytes are in image native order (little endian) - if py3: - assert_equal(b[0], ord(b'\xe0')) - assert_equal(b[1], ord(b'\x01')) - else: - assert_equal(b[0], b'\xe0') - assert_equal(b[1], b'\x01') - - -def test_big_endian(): - im = Image.open('Tests/images/16bit.MM.cropped.tif') - assert_equal(im.getpixel((0,0)), 480) - assert_equal(im.mode, 'I;16B') - - b = im.tobytes() - - # Bytes are in image native order (big endian) - if py3: - assert_equal(b[0], ord(b'\x01')) - assert_equal(b[1], ord(b'\xe0')) - else: - assert_equal(b[0], b'\x01') - assert_equal(b[1], b'\xe0') - - -def test_12bit_rawmode(): - """ Are we generating the same interpretation of the image as Imagemagick is? """ - - #Image.DEBUG = True - im = Image.open('Tests/images/12bit.cropped.tif') - - # to make the target -- - # convert 12bit.cropped.tif -depth 16 tmp.tif - # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif - # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. - - im2 = Image.open('Tests/images/12in16bit.tif') - - if Image.DEBUG: - print (im.getpixel((0,0))) - print (im.getpixel((0,1))) - print (im.getpixel((0,2))) - - print (im2.getpixel((0,0))) - print (im2.getpixel((0,1))) - print (im2.getpixel((0,2))) - - assert_image_equal(im, im2) - -def test_32bit_float(): - # Issue 614, specific 32 bit float format - path = 'Tests/images/10ct_32bit_128.tiff' - im = Image.open(path) - im.load() - - assert_equal(im.getpixel((0,0)), -0.4526388943195343) - assert_equal(im.getextrema(), (-3.140936851501465, 3.140684127807617)) - - +# End of file diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index c759d8c0d..2019f3455 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -1,80 +1,93 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena + from PIL import Image, TiffImagePlugin, TiffTags tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys())) -def test_rt_metadata(): - """ Test writing arbitray metadata into the tiff image directory - Use case is ImageJ private tags, one numeric, one arbitrary - data. https://github.com/python-pillow/Pillow/issues/291 - """ - img = lena() +class TestFileTiffMetadata(PillowTestCase): - textdata = "This is some arbitrary metadata for a text field" - info = TiffImagePlugin.ImageFileDirectory() + def test_rt_metadata(self): + """ Test writing arbitray metadata into the tiff image directory + Use case is ImageJ private tags, one numeric, one arbitrary + data. https://github.com/python-pillow/Pillow/issues/291 + """ - info[tag_ids['ImageJMetaDataByteCounts']] = len(textdata) - info[tag_ids['ImageJMetaData']] = textdata + img = lena() - f = tempfile("temp.tif") + textdata = "This is some arbitrary metadata for a text field" + info = TiffImagePlugin.ImageFileDirectory() - img.save(f, tiffinfo=info) + info[tag_ids['ImageJMetaDataByteCounts']] = len(textdata) + info[tag_ids['ImageJMetaData']] = textdata - loaded = Image.open(f) + f = self.tempfile("temp.tif") - assert_equal(loaded.tag[50838], (len(textdata),)) - assert_equal(loaded.tag[50839], textdata) + img.save(f, tiffinfo=info) -def test_read_metadata(): - img = Image.open('Tests/images/lena_g4.tif') + loaded = Image.open(f) - known = {'YResolution': ((1207959552, 16777216),), - 'PlanarConfiguration': (1,), - 'BitsPerSample': (1,), - 'ImageLength': (128,), - 'Compression': (4,), - 'FillOrder': (1,), - 'DocumentName': 'lena.g4.tif', - 'RowsPerStrip': (128,), - 'ResolutionUnit': (1,), - 'PhotometricInterpretation': (0,), - 'PageNumber': (0, 1), - 'XResolution': ((1207959552, 16777216),), - 'ImageWidth': (128,), - 'Orientation': (1,), - 'StripByteCounts': (1796,), - 'SamplesPerPixel': (1,), - 'StripOffsets': (8,), - 'Software': 'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} + self.assertEqual(loaded.tag[50838], (len(textdata),)) + self.assertEqual(loaded.tag[50839], textdata) - # assert_equal is equivalent, but less helpful in telling what's wrong. - named = img.tag.named() - for tag, value in named.items(): - assert_equal(known[tag], value) + def test_read_metadata(self): + img = Image.open('Tests/images/lena_g4.tif') - for tag, value in known.items(): - assert_equal(value, named[tag]) + known = {'YResolution': ((1207959552, 16777216),), + 'PlanarConfiguration': (1,), + 'BitsPerSample': (1,), + 'ImageLength': (128,), + 'Compression': (4,), + 'FillOrder': (1,), + 'DocumentName': 'lena.g4.tif', + 'RowsPerStrip': (128,), + 'ResolutionUnit': (1,), + 'PhotometricInterpretation': (0,), + 'PageNumber': (0, 1), + 'XResolution': ((1207959552, 16777216),), + 'ImageWidth': (128,), + 'Orientation': (1,), + 'StripByteCounts': (1796,), + 'SamplesPerPixel': (1,), + 'StripOffsets': (8,), + 'Software': 'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} + + # self.assertEqual is equivalent, + # but less helpful in telling what's wrong. + named = img.tag.named() + for tag, value in named.items(): + self.assertEqual(known[tag], value) + + for tag, value in known.items(): + self.assertEqual(value, named[tag]) + + def test_write_metadata(self): + """ Test metadata writing through the python code """ + img = Image.open('Tests/images/lena.tif') + + f = self.tempfile('temp.tiff') + img.save(f, tiffinfo=img.tag) + + loaded = Image.open(f) + + original = img.tag.named() + reloaded = loaded.tag.named() + + ignored = [ + 'StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] + + for tag, value in reloaded.items(): + if tag not in ignored: + self.assertEqual( + original[tag], value, "%s didn't roundtrip" % tag) + + for tag, value in original.items(): + if tag not in ignored: + self.assertEqual( + value, reloaded[tag], "%s didn't roundtrip" % tag) -def test_write_metadata(): - """ Test metadata writing through the python code """ - img = Image.open('Tests/images/lena.tif') +if __name__ == '__main__': + unittest.main() - f = tempfile('temp.tiff') - img.save(f, tiffinfo = img.tag) - - loaded = Image.open(f) - - original = img.tag.named() - reloaded = loaded.tag.named() - - ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] - - for tag, value in reloaded.items(): - if tag not in ignored: - assert_equal(original[tag], value, "%s didn't roundtrip" % tag) - - for tag, value in original.items(): - if tag not in ignored: - assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) +# End of file diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index a89dea3c4..ea1c2e19f 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,68 +1,79 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image - try: from PIL import _webp except: - skip('webp support not installed') + # Skip in setUp() + pass -def test_version(): - assert_no_exception(lambda: _webp.WebPDecoderVersion()) - assert_no_exception(lambda: _webp.WebPDecoderBuggyAlpha()) +class TestFileWebp(PillowTestCase): -def test_read_rgb(): + def setUp(self): + try: + from PIL import _webp + except: + self.skipTest('WebP support not installed') - file_path = "Images/lena.webp" - image = Image.open(file_path) + def test_version(self): + _webp.WebPDecoderVersion() + _webp.WebPDecoderBuggyAlpha() - assert_equal(image.mode, "RGB") - assert_equal(image.size, (128, 128)) - assert_equal(image.format, "WEBP") - assert_no_exception(lambda: image.load()) - assert_no_exception(lambda: image.getdata()) - - # generated with: dwebp -ppm ../../Images/lena.webp -o lena_webp_bits.ppm - target = Image.open('Tests/images/lena_webp_bits.ppm') - assert_image_similar(image, target, 20.0) - - -def test_write_rgb(): - """ - Can we write a RGB mode file to webp without error. Does it have the bits we - expect? - - """ - - temp_file = tempfile("temp.webp") - - lena("RGB").save(temp_file) - - image = Image.open(temp_file) - image.load() - - assert_equal(image.mode, "RGB") - assert_equal(image.size, (128, 128)) - assert_equal(image.format, "WEBP") - assert_no_exception(lambda: image.load()) - assert_no_exception(lambda: image.getdata()) - - # If we're using the exact same version of webp, this test should pass. - # but it doesn't if the webp is generated on Ubuntu and tested on Fedora. - - # generated with: dwebp -ppm temp.webp -o lena_webp_write.ppm - #target = Image.open('Tests/images/lena_webp_write.ppm') - #assert_image_equal(image, target) - - # This test asserts that the images are similar. If the average pixel difference - # between the two images is less than the epsilon value, then we're going to - # accept that it's a reasonable lossy version of the image. The included lena images - # for webp are showing ~16 on Ubuntu, the jpegs are showing ~18. - target = lena("RGB") - assert_image_similar(image, target, 20.0) + def test_read_rgb(self): + file_path = "Images/lena.webp" + image = Image.open(file_path) + + self.assertEqual(image.mode, "RGB") + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + + # generated with: + # dwebp -ppm ../../Images/lena.webp -o lena_webp_bits.ppm + target = Image.open('Tests/images/lena_webp_bits.ppm') + self.assert_image_similar(image, target, 20.0) + + def test_write_rgb(self): + """ + Can we write a RGB mode file to webp without error. + Does it have the bits we expect? + """ + + temp_file = self.tempfile("temp.webp") + + lena("RGB").save(temp_file) + + image = Image.open(temp_file) + image.load() + + self.assertEqual(image.mode, "RGB") + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + + # If we're using the exact same version of WebP, this test should pass. + # but it doesn't if the WebP is generated on Ubuntu and tested on + # Fedora. + + # generated with: dwebp -ppm temp.webp -o lena_webp_write.ppm + # target = Image.open('Tests/images/lena_webp_write.ppm') + # self.assert_image_equal(image, target) + + # This test asserts that the images are similar. If the average pixel + # difference between the two images is less than the epsilon value, + # then we're going to accept that it's a reasonable lossy version of + # the image. The included lena images for WebP are showing ~16 on + # Ubuntu, the jpegs are showing ~18. + target = lena("RGB") + self.assert_image_similar(image, target, 20.0) +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 5ac03b9d4..c9787c60e 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,82 +1,91 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image try: from PIL import _webp except: - skip('webp support not installed') + pass + # Skip in setUp() -if _webp.WebPDecoderBuggyAlpha(): - skip("Buggy early version of webp installed, not testing transparency") +class TestFileWebpAlpha(PillowTestCase): -def test_read_rgba(): - # Generated with `cwebp transparent.png -o transparent.webp` - file_path = "Images/transparent.webp" - image = Image.open(file_path) + def setUp(self): + try: + from PIL import _webp + except: + self.skipTest('WebP support not installed') - assert_equal(image.mode, "RGBA") - assert_equal(image.size, (200, 150)) - assert_equal(image.format, "WEBP") - assert_no_exception(lambda: image.load()) - assert_no_exception(lambda: image.getdata()) + if _webp.WebPDecoderBuggyAlpha(self): + self.skipTest("Buggy early version of WebP installed, not testing transparency") - orig_bytes = image.tobytes() - - target = Image.open('Images/transparent.png') - assert_image_similar(image, target, 20.0) - - -def test_write_lossless_rgb(): - temp_file = tempfile("temp.webp") - #temp_file = "temp.webp" - - pil_image = lena('RGBA') - - mask = Image.new("RGBA", (64, 64), (128,128,128,128)) - pil_image.paste(mask, (0,0), mask) # add some partially transparent bits. - - pil_image.save(temp_file, lossless=True) - - image = Image.open(temp_file) - image.load() - - assert_equal(image.mode, "RGBA") - assert_equal(image.size, pil_image.size) - assert_equal(image.format, "WEBP") - assert_no_exception(lambda: image.load()) - assert_no_exception(lambda: image.getdata()) - - - assert_image_equal(image, pil_image) - -def test_write_rgba(): - """ - Can we write a RGBA mode file to webp without error. Does it have the bits we - expect? - - """ - - temp_file = tempfile("temp.webp") - - pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20)) - pil_image.save(temp_file) - - if _webp.WebPDecoderBuggyAlpha(): - return - - image = Image.open(temp_file) - image.load() - - assert_equal(image.mode, "RGBA") - assert_equal(image.size, (10, 10)) - assert_equal(image.format, "WEBP") - assert_no_exception(image.load) - assert_no_exception(image.getdata) - - assert_image_similar(image, pil_image, 1.0) + def test_read_rgba(self): + # Generated with `cwebp transparent.png -o transparent.webp` + file_path = "Images/transparent.webp" + image = Image.open(file_path) + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, (200, 150)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + + image.tobytes() + + target = Image.open('Images/transparent.png') + self.assert_image_similar(image, target, 20.0) + + def test_write_lossless_rgb(self): + temp_file = self.tempfile("temp.webp") + # temp_file = "temp.webp" + + pil_image = lena('RGBA') + + mask = Image.new("RGBA", (64, 64), (128, 128, 128, 128)) + # Add some partially transparent bits: + pil_image.paste(mask, (0, 0), mask) + + pil_image.save(temp_file, lossless=True) + + image = Image.open(temp_file) + image.load() + + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, pil_image.size) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + + self.assert_image_equal(image, pil_image) + + def test_write_rgba(self): + """ + Can we write a RGBA mode file to webp without error. + Does it have the bits we expect? + """ + + temp_file = self.tempfile("temp.webp") + + pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20)) + pil_image.save(temp_file) + + if _webp.WebPDecoderBuggyAlpha(self): + return + + image = Image.open(temp_file) + image.load() + + self.assertEqual(image.mode, "RGBA") + self.assertEqual(image.size, (10, 10)) + self.assertEqual(image.format, "WEBP") + image.load + image.getdata + + self.assert_image_similar(image, pil_image, 1.0) +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index ca2b5af19..9f8e339de 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,33 +1,43 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image - try: from PIL import _webp except: - skip('webp support not installed') + pass + # Skip in setUp() -if (_webp.WebPDecoderVersion() < 0x0200): - skip('lossless not included') +class TestFileWebpLossless(PillowTestCase): -def test_write_lossless_rgb(): - temp_file = tempfile("temp.webp") + def setUp(self): + try: + from PIL import _webp + except: + self.skipTest('WebP support not installed') - lena("RGB").save(temp_file, lossless=True) + if (_webp.WebPDecoderVersion() < 0x0200): + self.skipTest('lossless not included') - image = Image.open(temp_file) - image.load() + def test_write_lossless_rgb(self): + temp_file = self.tempfile("temp.webp") - assert_equal(image.mode, "RGB") - assert_equal(image.size, (128, 128)) - assert_equal(image.format, "WEBP") - assert_no_exception(lambda: image.load()) - assert_no_exception(lambda: image.getdata()) - - - assert_image_equal(image, lena("RGB")) + lena("RGB").save(temp_file, lossless=True) + + image = Image.open(temp_file) + image.load() + + self.assertEqual(image.mode, "RGB") + self.assertEqual(image.size, (128, 128)) + self.assertEqual(image.format, "WEBP") + image.load() + image.getdata() + + self.assert_image_equal(image, lena("RGB")) +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index b4146c3ee..93021ac07 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,101 +1,112 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -try: - from PIL import _webp - if not _webp.HAVE_WEBPMUX: - skip('webpmux support not installed') -except: - skip('webp support not installed') + +class TestFileWebpMetadata(PillowTestCase): + + def setUp(self): + try: + from PIL import _webp + if not _webp.HAVE_WEBPMUX: + self.skipTest('webpmux support not installed') + except: + self.skipTest('WebP support not installed') + + def test_read_exif_metadata(self): + + file_path = "Images/flower.webp" + image = Image.open(file_path) + + self.assertEqual(image.format, "WEBP") + exif_data = image.info.get("exif", None) + self.assertTrue(exif_data) + + exif = image._getexif() + + # camera make + self.assertEqual(exif[271], "Canon") + + jpeg_image = Image.open('Tests/images/flower.jpg') + expected_exif = jpeg_image.info['exif'] + + self.assertEqual(exif_data, expected_exif) + + def test_write_exif_metadata(self): + from io import BytesIO + + file_path = "Tests/images/flower.jpg" + image = Image.open(file_path) + expected_exif = image.info['exif'] + + buffer = BytesIO() + + image.save(buffer, "webp", exif=expected_exif) + + buffer.seek(0) + webp_image = Image.open(buffer) + + webp_exif = webp_image.info.get('exif', None) + self.assertTrue(webp_exif) + if webp_exif: + self.assertEqual( + webp_exif, expected_exif, "WebP EXIF didn't match") + + def test_read_icc_profile(self): + + file_path = "Images/flower2.webp" + image = Image.open(file_path) + + self.assertEqual(image.format, "WEBP") + self.assertTrue(image.info.get("icc_profile", None)) + + icc = image.info['icc_profile'] + + jpeg_image = Image.open('Tests/images/flower2.jpg') + expected_icc = jpeg_image.info['icc_profile'] + + self.assertEqual(icc, expected_icc) + + def test_write_icc_metadata(self): + from io import BytesIO + + file_path = "Tests/images/flower2.jpg" + image = Image.open(file_path) + expected_icc_profile = image.info['icc_profile'] + + buffer = BytesIO() + + image.save(buffer, "webp", icc_profile=expected_icc_profile) + + buffer.seek(0) + webp_image = Image.open(buffer) + + webp_icc_profile = webp_image.info.get('icc_profile', None) + + self.assertTrue(webp_icc_profile) + if webp_icc_profile: + self.assertEqual( + webp_icc_profile, expected_icc_profile, + "Webp ICC didn't match") + + def test_read_no_exif(self): + from io import BytesIO + + file_path = "Tests/images/flower.jpg" + image = Image.open(file_path) + image.info['exif'] + + buffer = BytesIO() + + image.save(buffer, "webp") + + buffer.seek(0) + webp_image = Image.open(buffer) + + self.assertFalse(webp_image._getexif()) +if __name__ == '__main__': + unittest.main() -def test_read_exif_metadata(): - - file_path = "Images/flower.webp" - image = Image.open(file_path) - - assert_equal(image.format, "WEBP") - exif_data = image.info.get("exif", None) - assert_true(exif_data) - - exif = image._getexif() - - #camera make - assert_equal(exif[271], "Canon") - - jpeg_image = Image.open('Tests/images/flower.jpg') - expected_exif = jpeg_image.info['exif'] - - assert_equal(exif_data, expected_exif) - - -def test_write_exif_metadata(): - file_path = "Tests/images/flower.jpg" - image = Image.open(file_path) - expected_exif = image.info['exif'] - - buffer = BytesIO() - - image.save(buffer, "webp", exif=expected_exif) - - buffer.seek(0) - webp_image = Image.open(buffer) - - webp_exif = webp_image.info.get('exif', None) - assert_true(webp_exif) - if webp_exif: - assert_equal(webp_exif, expected_exif, "Webp Exif didn't match") - - -def test_read_icc_profile(): - - file_path = "Images/flower2.webp" - image = Image.open(file_path) - - assert_equal(image.format, "WEBP") - assert_true(image.info.get("icc_profile", None)) - - icc = image.info['icc_profile'] - - jpeg_image = Image.open('Tests/images/flower2.jpg') - expected_icc = jpeg_image.info['icc_profile'] - - assert_equal(icc, expected_icc) - - -def test_write_icc_metadata(): - file_path = "Tests/images/flower2.jpg" - image = Image.open(file_path) - expected_icc_profile = image.info['icc_profile'] - - buffer = BytesIO() - - image.save(buffer, "webp", icc_profile=expected_icc_profile) - - buffer.seek(0) - webp_image = Image.open(buffer) - - webp_icc_profile = webp_image.info.get('icc_profile', None) - - assert_true(webp_icc_profile) - if webp_icc_profile: - assert_equal(webp_icc_profile, expected_icc_profile, "Webp ICC didn't match") - - -def test_read_no_exif(): - file_path = "Tests/images/flower.jpg" - image = Image.open(file_path) - expected_exif = image.info['exif'] - - buffer = BytesIO() - - image.save(buffer, "webp") - - buffer.seek(0) - webp_image = Image.open(buffer) - - assert_false(webp_image._getexif()) - - +# End of file diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index f27a3a349..d520ef460 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -25,10 +25,20 @@ static char basic_bits[] = { }; """ -def test_pil151(): - im = Image.open(BytesIO(PIL151)) +class TestFileXbm(PillowTestCase): - assert_no_exception(lambda: im.load()) - assert_equal(im.mode, '1') - assert_equal(im.size, (32, 32)) + def test_pil151(self): + from io import BytesIO + + im = Image.open(BytesIO(PIL151)) + + im.load() + self.assertEqual(im.mode, '1') + self.assertEqual(im.size, (32, 32)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 44135d028..0c765da8e 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image @@ -6,9 +6,18 @@ from PIL import Image file = "Images/lena.xpm" data = open(file, "rb").read() -def test_sanity(): - im = Image.open(file) - im.load() - assert_equal(im.mode, "P") - assert_equal(im.size, (128, 128)) - assert_equal(im.format, "XPM") + +class TestFileXpm(PillowTestCase): + + def test_sanity(self): + im = Image.open(file) + im.load() + self.assertEqual(im.mode, "P") + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.format, "XPM") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 366bb4468..9a8f19d03 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,13 +1,22 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image, FontFile, BdfFontFile +from PIL import FontFile, BdfFontFile filename = "Images/courB08.bdf" -def test_sanity(): - file = open(filename, "rb") - font = BdfFontFile.BdfFontFile(file) +class TestFontBdf(PillowTestCase): - assert_true(isinstance(font, FontFile.FontFile)) - assert_equal(len([_f for _f in font.glyph if _f]), 190) + def test_sanity(self): + + file = open(filename, "rb") + font = BdfFontFile.BdfFontFile(file) + + self.assertIsInstance(font, FontFile.FontFile) + self.assertEqual(len([_f for _f in font.glyph if _f]), 190) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index bae214e35..24abcf73d 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,49 +1,63 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image, FontFile, PcfFontFile from PIL import ImageFont, ImageDraw codecs = dir(Image.core) -if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - skip("zlib support not available") - fontname = "Tests/fonts/helvO18.pcf" -tempname = tempfile("temp.pil", "temp.pbm") -message = "hello, world" +message = "hello, world" -def test_sanity(): - file = open(fontname, "rb") - font = PcfFontFile.PcfFontFile(file) - assert_true(isinstance(font, FontFile.FontFile)) - assert_equal(len([_f for _f in font.glyph if _f]), 192) +class TestFontPcf(PillowTestCase): - font.save(tempname) + def setUp(self): + if "zip_encoder" not in codecs or "zip_decoder" not in codecs: + self.skipTest("zlib support not available") -def xtest_draw(): + def save_font(self): + file = open(fontname, "rb") + font = PcfFontFile.PcfFontFile(file) + self.assertIsInstance(font, FontFile.FontFile) + self.assertEqual(len([_f for _f in font.glyph if _f]), 192) - font = ImageFont.load(tempname) - image = Image.new("L", font.getsize(message), "white") - draw = ImageDraw.Draw(image) - draw.text((0, 0), message, font=font) - # assert_signature(image, "7216c60f988dea43a46bb68321e3c1b03ec62aee") + tempname = self.tempfile("temp.pil", "temp.pbm") + font.save(tempname) + return tempname -def _test_high_characters(message): + def test_sanity(self): + self.save_font() - font = ImageFont.load(tempname) - image = Image.new("L", font.getsize(message), "white") - draw = ImageDraw.Draw(image) - draw.text((0, 0), message, font=font) + def xtest_draw(self): - compare = Image.open('Tests/images/high_ascii_chars.png') - assert_image_equal(image, compare) + tempname = self.save_font() + font = ImageFont.load(tempname) + image = Image.new("L", font.getsize(message), "white") + draw = ImageDraw.Draw(image) + draw.text((0, 0), message, font=font) + # assert_signature(image, "7216c60f988dea43a46bb68321e3c1b03ec62aee") -def test_high_characters(): - message = "".join([chr(i+1) for i in range(140,232)]) - _test_high_characters(message) - # accept bytes instances in Py3. - if bytes is not str: - _test_high_characters(message.encode('latin1')) + def _test_high_characters(self, message): + tempname = self.save_font() + font = ImageFont.load(tempname) + image = Image.new("L", font.getsize(message), "white") + draw = ImageDraw.Draw(image) + draw.text((0, 0), message, font=font) + + compare = Image.open('Tests/images/high_ascii_chars.png') + self.assert_image_equal(image, compare) + + def test_high_characters(self): + message = "".join([chr(i+1) for i in range(140, 232)]) + self._test_high_characters(message) + # accept bytes instances in Py3. + if bytes is not str: + self._test_high_characters(message.encode('latin1')) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py index 371b06a0b..188b0d1fa 100644 --- a/Tests/test_format_lab.py +++ b/Tests/test_format_lab.py @@ -1,41 +1,48 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -def test_white(): - i = Image.open('Tests/images/lab.tif') - bits = i.load() - - assert_equal(i.mode, 'LAB') +class TestFormatLab(PillowTestCase): - assert_equal(i.getbands(), ('L','A', 'B')) + def test_white(self): + i = Image.open('Tests/images/lab.tif') - k = i.getpixel((0,0)) - assert_equal(k, (255,128,128)) + i.load() - L = i.getdata(0) - a = i.getdata(1) - b = i.getdata(2) + self.assertEqual(i.mode, 'LAB') - assert_equal(list(L), [255]*100) - assert_equal(list(a), [128]*100) - assert_equal(list(b), [128]*100) - + self.assertEqual(i.getbands(), ('L', 'A', 'B')) -def test_green(): - # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS - # == RGB: 0, 152, 117 - i = Image.open('Tests/images/lab-green.tif') + k = i.getpixel((0, 0)) + self.assertEqual(k, (255, 128, 128)) - k = i.getpixel((0,0)) - assert_equal(k, (128,28,128)) + L = i.getdata(0) + a = i.getdata(1) + b = i.getdata(2) + + self.assertEqual(list(L), [255]*100) + self.assertEqual(list(a), [128]*100) + self.assertEqual(list(b), [128]*100) + + def test_green(self): + # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS + # == RGB: 0, 152, 117 + i = Image.open('Tests/images/lab-green.tif') + + k = i.getpixel((0, 0)) + self.assertEqual(k, (128, 28, 128)) + + def test_red(self): + # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS + # == RGB: 255, 0, 124 + i = Image.open('Tests/images/lab-red.tif') + + k = i.getpixel((0, 0)) + self.assertEqual(k, (128, 228, 128)) -def test_red(): - # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS - # == RGB: 255, 0, 124 - i = Image.open('Tests/images/lab-red.tif') +if __name__ == '__main__': + unittest.main() - k = i.getpixel((0,0)) - assert_equal(k, (128,228,128)) +# End of file diff --git a/Tests/test_image.py b/Tests/test_image.py index 26c699e66..ec82eb419 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,39 +1,51 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -def test_sanity(): - im = Image.new("L", (100, 100)) - assert_equal(repr(im)[:45], " 8 bit lut for converting I->L images - see https://github.com/python-pillow/Pillow/issues/440 - """ +class TestImagePoint(PillowTestCase): - im = lena("I") - assert_no_exception(lambda: im.point(list(range(256))*256, 'L')) + def setUp(self): + if hasattr(sys, 'pypy_version_info'): + # This takes _forever_ on PyPy. Open Bug, + # see https://github.com/python-pillow/Pillow/issues/484 + self.skipTest("Too slow on PyPy") + + def test_sanity(self): + im = lena() + + self.assertRaises(ValueError, lambda: im.point(list(range(256)))) + im.point(list(range(256))*3) + im.point(lambda x: x) + + im = im.convert("I") + self.assertRaises(ValueError, lambda: im.point(list(range(256)))) + im.point(lambda x: x*1) + im.point(lambda x: x+1) + im.point(lambda x: x*1+1) + self.assertRaises(TypeError, lambda: im.point(lambda x: x-1)) + self.assertRaises(TypeError, lambda: im.point(lambda x: x/1)) + + def test_16bit_lut(self): + """ Tests for 16 bit -> 8 bit lut for converting I->L images + see https://github.com/python-pillow/Pillow/issues/440 + """ + + im = lena("I") + im.point(list(range(256))*256, 'L') + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py index b23f69834..bb36b335e 100644 --- a/Tests/test_image_putalpha.py +++ b/Tests/test_image_putalpha.py @@ -1,43 +1,52 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -def test_interface(): - im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 0)) +class TestImagePutAlpha(PillowTestCase): - im = Image.new("RGBA", (1, 1), (1, 2, 3)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 255)) + def test_interface(self): - im.putalpha(Image.new("L", im.size, 4)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) + im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 0)) - im.putalpha(5) - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 5)) + im = Image.new("RGBA", (1, 1), (1, 2, 3)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 255)) -def test_promote(): + im.putalpha(Image.new("L", im.size, 4)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) - im = Image.new("L", (1, 1), 1) - assert_equal(im.getpixel((0, 0)), 1) + im.putalpha(5) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 5)) - im.putalpha(2) - assert_equal(im.mode, 'LA') - assert_equal(im.getpixel((0, 0)), (1, 2)) + def test_promote(self): - im = Image.new("RGB", (1, 1), (1, 2, 3)) - assert_equal(im.getpixel((0, 0)), (1, 2, 3)) + im = Image.new("L", (1, 1), 1) + self.assertEqual(im.getpixel((0, 0)), 1) - im.putalpha(4) - assert_equal(im.mode, 'RGBA') - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) + im.putalpha(2) + self.assertEqual(im.mode, 'LA') + self.assertEqual(im.getpixel((0, 0)), (1, 2)) -def test_readonly(): + im = Image.new("RGB", (1, 1), (1, 2, 3)) + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3)) - im = Image.new("RGB", (1, 1), (1, 2, 3)) - im.readonly = 1 + im.putalpha(4) + self.assertEqual(im.mode, 'RGBA') + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) - im.putalpha(4) - assert_false(im.readonly) - assert_equal(im.mode, 'RGBA') - assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) + def test_readonly(self): + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + im.readonly = 1 + + im.putalpha(4) + self.assertFalse(im.readonly) + self.assertEqual(im.mode, 'RGBA') + self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index e25359fdf..d792adfe6 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -1,40 +1,48 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena import sys from PIL import Image -def test_sanity(): - im1 = lena() +class TestImagePutData(PillowTestCase): - data = list(im1.getdata()) + def test_sanity(self): - im2 = Image.new(im1.mode, im1.size, 0) - im2.putdata(data) + im1 = lena() - assert_image_equal(im1, im2) + data = list(im1.getdata()) - # readonly - im2 = Image.new(im1.mode, im2.size, 0) - im2.readonly = 1 - im2.putdata(data) + im2 = Image.new(im1.mode, im1.size, 0) + im2.putdata(data) - assert_false(im2.readonly) - assert_image_equal(im1, im2) + self.assert_image_equal(im1, im2) + + # readonly + im2 = Image.new(im1.mode, im2.size, 0) + im2.readonly = 1 + im2.putdata(data) + + self.assertFalse(im2.readonly) + self.assert_image_equal(im1, im2) + + def test_long_integers(self): + # see bug-200802-systemerror + def put(value): + im = Image.new("RGBA", (1, 1)) + im.putdata([value]) + return im.getpixel((0, 0)) + self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) + self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) + self.assertEqual(put(-1), (255, 255, 255, 255)) + self.assertEqual(put(-1), (255, 255, 255, 255)) + if sys.maxsize > 2**32: + self.assertEqual(put(sys.maxsize), (255, 255, 255, 255)) + else: + self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) -def test_long_integers(): - # see bug-200802-systemerror - def put(value): - im = Image.new("RGBA", (1, 1)) - im.putdata([value]) - return im.getpixel((0, 0)) - assert_equal(put(0xFFFFFFFF), (255, 255, 255, 255)) - assert_equal(put(0xFFFFFFFF), (255, 255, 255, 255)) - assert_equal(put(-1), (255, 255, 255, 255)) - assert_equal(put(-1), (255, 255, 255, 255)) - if sys.maxsize > 2**32: - assert_equal(put(sys.maxsize), (255, 255, 255, 255)) - else: - assert_equal(put(sys.maxsize), (255, 255, 255, 127)) +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index b7ebb8853..26ad09800 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -1,28 +1,36 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena -from PIL import Image from PIL import ImagePalette -def test_putpalette(): - def palette(mode): - im = lena(mode).copy() - im.putpalette(list(range(256))*3) - p = im.getpalette() - if p: - return im.mode, p[:10] - return im.mode - assert_exception(ValueError, lambda: palette("1")) - assert_equal(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - assert_equal(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - assert_exception(ValueError, lambda: palette("I")) - assert_exception(ValueError, lambda: palette("F")) - assert_exception(ValueError, lambda: palette("RGB")) - assert_exception(ValueError, lambda: palette("RGBA")) - assert_exception(ValueError, lambda: palette("YCbCr")) -def test_imagepalette(): - im = lena("P") - assert_no_exception(lambda: im.putpalette(ImagePalette.negative())) - assert_no_exception(lambda: im.putpalette(ImagePalette.random())) - assert_no_exception(lambda: im.putpalette(ImagePalette.sepia())) - assert_no_exception(lambda: im.putpalette(ImagePalette.wedge())) +class TestImagePutPalette(PillowTestCase): + + def test_putpalette(self): + def palette(mode): + im = lena(mode).copy() + im.putpalette(list(range(256))*3) + p = im.getpalette() + if p: + return im.mode, p[:10] + return im.mode + self.assertRaises(ValueError, lambda: palette("1")) + self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + self.assertRaises(ValueError, lambda: palette("I")) + self.assertRaises(ValueError, lambda: palette("F")) + self.assertRaises(ValueError, lambda: palette("RGB")) + self.assertRaises(ValueError, lambda: palette("RGBA")) + self.assertRaises(ValueError, lambda: palette("YCbCr")) + + def test_imagepalette(self): + im = lena("P") + im.putpalette(ImagePalette.negative()) + im.putpalette(ImagePalette.random()) + im.putpalette(ImagePalette.sepia()) + im.putpalette(ImagePalette.wedge()) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_putpixel.py b/Tests/test_image_putpixel.py index 5f19237cb..1afc013c0 100644 --- a/Tests/test_image_putpixel.py +++ b/Tests/test_image_putpixel.py @@ -1,45 +1,50 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -Image.USE_CFFI_ACCESS=False - -def test_sanity(): - - im1 = lena() - im2 = Image.new(im1.mode, im1.size, 0) - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pos = x, y - im2.putpixel(pos, im1.getpixel(pos)) - - assert_image_equal(im1, im2) - - im2 = Image.new(im1.mode, im1.size, 0) - im2.readonly = 1 - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pos = x, y - im2.putpixel(pos, im1.getpixel(pos)) - - assert_false(im2.readonly) - assert_image_equal(im1, im2) - - im2 = Image.new(im1.mode, im1.size, 0) - - pix1 = im1.load() - pix2 = im2.load() - - for y in range(im1.size[1]): - for x in range(im1.size[0]): - pix2[x, y] = pix1[x, y] - - assert_image_equal(im1, im2) +Image.USE_CFFI_ACCESS = False +class TestImagePutPixel(PillowTestCase): + + def test_sanity(self): + + im1 = lena() + im2 = Image.new(im1.mode, im1.size, 0) + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + self.assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + im2.readonly = 1 + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pos = x, y + im2.putpixel(pos, im1.getpixel(pos)) + + self.assertFalse(im2.readonly) + self.assert_image_equal(im1, im2) + + im2 = Image.new(im1.mode, im1.size, 0) + + pix1 = im1.load() + pix2 = im2.load() + + for y in range(im1.size[1]): + for x in range(im1.size[0]): + pix2[x, y] = pix1[x, y] + + self.assert_image_equal(im1, im2) + + # see test_image_getpixel for more tests -# see test_image_getpixel for more tests +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index dbf68a25e..63fe0cb21 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,27 +1,35 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -def test_sanity(): - im = lena() +class TestImageQuantize(PillowTestCase): - im = im.quantize() - assert_image(im, "P", im.size) + def test_sanity(self): + im = lena() - im = lena() - im = im.quantize(palette=lena("P")) - assert_image(im, "P", im.size) + im = im.quantize() + self.assert_image(im, "P", im.size) -def test_octree_quantize(): - im = lena() + im = lena() + im = im.quantize(palette=lena("P")) + self.assert_image(im, "P", im.size) - im = im.quantize(100, Image.FASTOCTREE) - assert_image(im, "P", im.size) + def test_octree_quantize(self): + im = lena() - assert len(im.getcolors()) == 100 + im = im.quantize(100, Image.FASTOCTREE) + self.assert_image(im, "P", im.size) -def test_rgba_quantize(): - im = lena('RGBA') - assert_no_exception(lambda: im.quantize()) - assert_exception(Exception, lambda: im.quantize(method=0)) + assert len(im.getcolors()) == 100 + + def test_rgba_quantize(self): + im = lena('RGBA') + im.quantize() + self.assertRaises(Exception, lambda: im.quantize(method=0)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 4e228a396..a200b17b4 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -1,12 +1,19 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena -from PIL import Image -def test_resize(): - def resize(mode, size): - out = lena(mode).resize(size) - assert_equal(out.mode, mode) - assert_equal(out.size, size) - for mode in "1", "P", "L", "RGB", "I", "F": - yield_test(resize, mode, (100, 100)) - yield_test(resize, mode, (200, 200)) +class TestImageResize(PillowTestCase): + + def test_resize(self): + def resize(mode, size): + out = lena(mode).resize(size) + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, size) + for mode in "1", "P", "L", "RGB", "I", "F": + resize(mode, (100, 100)) + resize(mode, (200, 200)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 5e4782c87..bb24ddf4f 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -1,15 +1,22 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena -from PIL import Image -def test_rotate(): - def rotate(mode): - im = lena(mode) - out = im.rotate(45) - assert_equal(out.mode, mode) - assert_equal(out.size, im.size) # default rotate clips output - out = im.rotate(45, expand=1) - assert_equal(out.mode, mode) - assert_true(out.size != im.size) - for mode in "1", "P", "L", "RGB", "I", "F": - yield_test(rotate, mode) +class TestImageRotate(PillowTestCase): + + def test_rotate(self): + def rotate(mode): + im = lena(mode) + out = im.rotate(45) + self.assertEqual(out.mode, mode) + self.assertEqual(out.size, im.size) # default rotate clips output + out = im.rotate(45, expand=1) + self.assertEqual(out.mode, mode) + self.assertNotEqual(out.size, im.size) + for mode in "1", "P", "L", "RGB", "I", "F": + rotate(mode) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index 07a779664..284acd87c 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,49 +1,67 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -def test_split(): - def split(mode): - layers = lena(mode).split() - return [(i.mode, i.size[0], i.size[1]) for i in layers] - assert_equal(split("1"), [('1', 128, 128)]) - assert_equal(split("L"), [('L', 128, 128)]) - assert_equal(split("I"), [('I', 128, 128)]) - assert_equal(split("F"), [('F', 128, 128)]) - assert_equal(split("P"), [('P', 128, 128)]) - assert_equal(split("RGB"), [('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) - assert_equal(split("RGBA"), [('L', 128, 128), ('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) - assert_equal(split("CMYK"), [('L', 128, 128), ('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) - assert_equal(split("YCbCr"), [('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) -def test_split_merge(): - def split_merge(mode): - return Image.merge(mode, lena(mode).split()) - assert_image_equal(lena("1"), split_merge("1")) - assert_image_equal(lena("L"), split_merge("L")) - assert_image_equal(lena("I"), split_merge("I")) - assert_image_equal(lena("F"), split_merge("F")) - assert_image_equal(lena("P"), split_merge("P")) - assert_image_equal(lena("RGB"), split_merge("RGB")) - assert_image_equal(lena("RGBA"), split_merge("RGBA")) - assert_image_equal(lena("CMYK"), split_merge("CMYK")) - assert_image_equal(lena("YCbCr"), split_merge("YCbCr")) +class TestImageSplit(PillowTestCase): -def test_split_open(): - codecs = dir(Image.core) + def test_split(self): + def split(mode): + layers = lena(mode).split() + return [(i.mode, i.size[0], i.size[1]) for i in layers] + self.assertEqual(split("1"), [('1', 128, 128)]) + self.assertEqual(split("L"), [('L', 128, 128)]) + self.assertEqual(split("I"), [('I', 128, 128)]) + self.assertEqual(split("F"), [('F', 128, 128)]) + self.assertEqual(split("P"), [('P', 128, 128)]) + self.assertEqual( + split("RGB"), [('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) + self.assertEqual( + split("RGBA"), + [('L', 128, 128), ('L', 128, 128), + ('L', 128, 128), ('L', 128, 128)]) + self.assertEqual( + split("CMYK"), + [('L', 128, 128), ('L', 128, 128), + ('L', 128, 128), ('L', 128, 128)]) + self.assertEqual( + split("YCbCr"), + [('L', 128, 128), ('L', 128, 128), ('L', 128, 128)]) - if 'zip_encoder' in codecs: - file = tempfile("temp.png") - else: - file = tempfile("temp.pcx") + def test_split_merge(self): + def split_merge(mode): + return Image.merge(mode, lena(mode).split()) + self.assert_image_equal(lena("1"), split_merge("1")) + self.assert_image_equal(lena("L"), split_merge("L")) + self.assert_image_equal(lena("I"), split_merge("I")) + self.assert_image_equal(lena("F"), split_merge("F")) + self.assert_image_equal(lena("P"), split_merge("P")) + self.assert_image_equal(lena("RGB"), split_merge("RGB")) + self.assert_image_equal(lena("RGBA"), split_merge("RGBA")) + self.assert_image_equal(lena("CMYK"), split_merge("CMYK")) + self.assert_image_equal(lena("YCbCr"), split_merge("YCbCr")) - def split_open(mode): - lena(mode).save(file) - im = Image.open(file) - return len(im.split()) - assert_equal(split_open("1"), 1) - assert_equal(split_open("L"), 1) - assert_equal(split_open("P"), 1) - assert_equal(split_open("RGB"), 3) - if 'zip_encoder' in codecs: - assert_equal(split_open("RGBA"), 4) + def test_split_open(self): + codecs = dir(Image.core) + + if 'zip_encoder' in codecs: + file = self.tempfile("temp.png") + else: + file = self.tempfile("temp.pcx") + + def split_open(mode): + lena(mode).save(file) + im = Image.open(file) + return len(im.split()) + self.assertEqual(split_open("1"), 1) + self.assertEqual(split_open("L"), 1) + self.assertEqual(split_open("P"), 1) + self.assertEqual(split_open("RGB"), 3) + if 'zip_encoder' in codecs: + self.assertEqual(split_open("RGBA"), 4) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 871dd1f54..6b33da318 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,36 +1,43 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena -from PIL import Image -def test_sanity(): +class TestImageThumbnail(PillowTestCase): - im = lena() - im.thumbnail((100, 100)) + def test_sanity(self): - assert_image(im, im.mode, (100, 100)) + im = lena() + im.thumbnail((100, 100)) -def test_aspect(): + self.assert_image(im, im.mode, (100, 100)) - im = lena() - im.thumbnail((100, 100)) - assert_image(im, im.mode, (100, 100)) + def test_aspect(self): - im = lena().resize((128, 256)) - im.thumbnail((100, 100)) - assert_image(im, im.mode, (50, 100)) + im = lena() + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (100, 100)) - im = lena().resize((128, 256)) - im.thumbnail((50, 100)) - assert_image(im, im.mode, (50, 100)) + im = lena().resize((128, 256)) + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (50, 100)) - im = lena().resize((256, 128)) - im.thumbnail((100, 100)) - assert_image(im, im.mode, (100, 50)) + im = lena().resize((128, 256)) + im.thumbnail((50, 100)) + self.assert_image(im, im.mode, (50, 100)) - im = lena().resize((256, 128)) - im.thumbnail((100, 50)) - assert_image(im, im.mode, (100, 50)) + im = lena().resize((256, 128)) + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (100, 50)) - im = lena().resize((128, 128)) - im.thumbnail((100, 100)) - assert_image(im, im.mode, (100, 100)) + im = lena().resize((256, 128)) + im.thumbnail((100, 50)) + self.assert_image(im, im.mode, (100, 50)) + + im = lena().resize((128, 128)) + im.thumbnail((100, 100)) + self.assert_image(im, im.mode, (100, 100)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py index 6fb10dd53..93f01c9de 100644 --- a/Tests/test_image_tobitmap.py +++ b/Tests/test_image_tobitmap.py @@ -1,15 +1,22 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena, fromstring -from PIL import Image -def test_sanity(): +class TestImageToBitmap(PillowTestCase): - assert_exception(ValueError, lambda: lena().tobitmap()) - assert_no_exception(lambda: lena().convert("1").tobitmap()) + def test_sanity(self): - im1 = lena().convert("1") + self.assertRaises(ValueError, lambda: lena().tobitmap()) + lena().convert("1").tobitmap() - bitmap = im1.tobitmap() + im1 = lena().convert("1") - assert_true(isinstance(bitmap, bytes)) - assert_image_equal(im1, fromstring(bitmap)) + bitmap = im1.tobitmap() + + self.assertIsInstance(bitmap, bytes) + self.assert_image_equal(im1, fromstring(bitmap)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py index d42399993..3be9128c1 100644 --- a/Tests/test_image_tobytes.py +++ b/Tests/test_image_tobytes.py @@ -1,7 +1,13 @@ -from tester import * +from helper import unittest, lena -from PIL import Image -def test_sanity(): - data = lena().tobytes() - assert_true(isinstance(data, bytes)) +class TestImageToBytes(unittest.TestCase): + + def test_sanity(self): + data = lena().tobytes() + self.assertTrue(isinstance(data, bytes)) + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index dd9b6fe5c..3f257fef2 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,116 +1,125 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -def test_extent(): - im = lena('RGB') - (w,h) = im.size - transformed = im.transform(im.size, Image.EXTENT, - (0,0, - w//2,h//2), # ul -> lr - Image.BILINEAR) + +class TestImageTransform(PillowTestCase): + + def test_extent(self): + im = lena('RGB') + (w, h) = im.size + transformed = im.transform(im.size, Image.EXTENT, + (0, 0, + w//2, h//2), # ul -> lr + Image.BILINEAR) + + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) + + # undone -- precision? + self.assert_image_similar(transformed, scaled, 10) + + def test_quad(self): + # one simple quad transform, equivalent to scale & crop upper left quad + im = lena('RGB') + (w, h) = im.size + transformed = im.transform(im.size, Image.QUAD, + (0, 0, 0, h//2, + # ul -> ccw around quad: + w//2, h//2, w//2, 0), + Image.BILINEAR) + + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) + + self.assert_image_equal(transformed, scaled) + + def test_mesh(self): + # this should be a checkerboard of halfsized lenas in ul, lr + im = lena('RGBA') + (w, h) = im.size + transformed = im.transform(im.size, Image.MESH, + [((0, 0, w//2, h//2), # box + (0, 0, 0, h, + w, h, w, 0)), # ul -> ccw around quad + ((w//2, h//2, w, h), # box + (0, 0, 0, h, + w, h, w, 0))], # ul -> ccw around quad + Image.BILINEAR) + + # transformed.save('transformed.png') + + scaled = im.resize((w//2, h//2), Image.BILINEAR) + + checker = Image.new('RGBA', im.size) + checker.paste(scaled, (0, 0)) + checker.paste(scaled, (w//2, h//2)) + + self.assert_image_equal(transformed, checker) + + # now, check to see that the extra area is (0, 0, 0, 0) + blank = Image.new('RGBA', (w//2, h//2), (0, 0, 0, 0)) + + self.assert_image_equal(blank, transformed.crop((w//2, 0, w, h//2))) + self.assert_image_equal(blank, transformed.crop((0, h//2, w//2, h))) + + def _test_alpha_premult(self, op): + # create image with half white, half black, + # with the black half transparent. + # do op, + # there should be no darkness in the white section. + im = Image.new('RGBA', (10, 10), (0, 0, 0, 0)) + im2 = Image.new('RGBA', (5, 10), (255, 255, 255, 255)) + im.paste(im2, (0, 0)) + + im = op(im, (40, 10)) + im_background = Image.new('RGB', (40, 10), (255, 255, 255)) + im_background.paste(im, (0, 0), im) + + hist = im_background.histogram() + self.assertEqual(40*10, hist[-1]) + + def test_alpha_premult_resize(self): + + def op(im, sz): + return im.resize(sz, Image.LINEAR) + + self._test_alpha_premult(op) + + def test_alpha_premult_transform(self): + + def op(im, sz): + (w, h) = im.size + return im.transform(sz, Image.EXTENT, + (0, 0, + w, h), + Image.BILINEAR) + + self._test_alpha_premult(op) + + def test_blank_fill(self): + # attempting to hit + # https://github.com/python-pillow/Pillow/issues/254 reported + # + # issue is that transforms with transparent overflow area + # contained junk from previous images, especially on systems with + # constrained memory. So, attempt to fill up memory with a + # pattern, free it, and then run the mesh test again. Using a 1Mp + # image with 4 bands, for 4 megs of data allocated, x 64. OMM (64 + # bit 12.04 VM with 512 megs available, this fails with Pillow < + # a0eaf06cc5f62a6fb6de556989ac1014ff3348ea + # + # Running by default, but I'd totally understand not doing it in + # the future + + foo = [Image.new('RGBA', (1024, 1024), (a, a, a, a)) + for a in range(1, 65)] + + # Yeah. Watch some JIT optimize this out. + foo = None + + self.test_mesh() - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) +if __name__ == '__main__': + unittest.main() - assert_image_similar(transformed, scaled, 10) # undone -- precision? - -def test_quad(): - # one simple quad transform, equivalent to scale & crop upper left quad - im = lena('RGB') - (w,h) = im.size - transformed = im.transform(im.size, Image.QUAD, - (0,0,0,h//2, - w//2,h//2,w//2,0), # ul -> ccw around quad - Image.BILINEAR) - - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - - assert_image_equal(transformed, scaled) - -def test_mesh(): - # this should be a checkerboard of halfsized lenas in ul, lr - im = lena('RGBA') - (w,h) = im.size - transformed = im.transform(im.size, Image.MESH, - [((0,0,w//2,h//2), # box - (0,0,0,h, - w,h,w,0)), # ul -> ccw around quad - ((w//2,h//2,w,h), # box - (0,0,0,h, - w,h,w,0))], # ul -> ccw around quad - Image.BILINEAR) - - #transformed.save('transformed.png') - - scaled = im.resize((w//2, h//2), Image.BILINEAR) - - checker = Image.new('RGBA', im.size) - checker.paste(scaled, (0,0)) - checker.paste(scaled, (w//2,h//2)) - - assert_image_equal(transformed, checker) - - # now, check to see that the extra area is (0,0,0,0) - blank = Image.new('RGBA', (w//2,h//2), (0,0,0,0)) - - assert_image_equal(blank, transformed.crop((w//2,0,w,h//2))) - assert_image_equal(blank, transformed.crop((0,h//2,w//2,h))) - -def _test_alpha_premult(op): - # create image with half white, half black, with the black half transparent. - # do op, - # there should be no darkness in the white section. - im = Image.new('RGBA', (10,10), (0,0,0,0)); - im2 = Image.new('RGBA', (5,10), (255,255,255,255)); - im.paste(im2, (0,0)) - - im = op(im, (40,10)) - im_background = Image.new('RGB', (40,10), (255,255,255)) - im_background.paste(im, (0,0), im) - - hist = im_background.histogram() - assert_equal(40*10, hist[-1]) - - -def test_alpha_premult_resize(): - - def op (im, sz): - return im.resize(sz, Image.LINEAR) - - _test_alpha_premult(op) - -def test_alpha_premult_transform(): - - def op(im, sz): - (w,h) = im.size - return im.transform(sz, Image.EXTENT, - (0,0, - w,h), - Image.BILINEAR) - - _test_alpha_premult(op) - - -def test_blank_fill(): - # attempting to hit - # https://github.com/python-pillow/Pillow/issues/254 reported - # - # issue is that transforms with transparent overflow area - # contained junk from previous images, especially on systems with - # constrained memory. So, attempt to fill up memory with a - # pattern, free it, and then run the mesh test again. Using a 1Mp - # image with 4 bands, for 4 megs of data allocated, x 64. OMM (64 - # bit 12.04 VM with 512 megs available, this fails with Pillow < - # a0eaf06cc5f62a6fb6de556989ac1014ff3348ea - # - # Running by default, but I'd totally understand not doing it in - # the future - - foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) - for a in range(1,65)] - - # Yeah. Watch some JIT optimize this out. - foo = None - - test_mesh() +# End of file diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index 43b3ef9d3..ec83aa3a6 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image @@ -8,27 +8,37 @@ ROTATE_90 = Image.ROTATE_90 ROTATE_180 = Image.ROTATE_180 ROTATE_270 = Image.ROTATE_270 -def test_sanity(): - im = lena() +class TestImageTranspose(PillowTestCase): - assert_no_exception(lambda: im.transpose(FLIP_LEFT_RIGHT)) - assert_no_exception(lambda: im.transpose(FLIP_TOP_BOTTOM)) + def test_sanity(self): - assert_no_exception(lambda: im.transpose(ROTATE_90)) - assert_no_exception(lambda: im.transpose(ROTATE_180)) - assert_no_exception(lambda: im.transpose(ROTATE_270)) + im = lena() -def test_roundtrip(): + im.transpose(FLIP_LEFT_RIGHT) + im.transpose(FLIP_TOP_BOTTOM) - im = lena() + im.transpose(ROTATE_90) + im.transpose(ROTATE_180) + im.transpose(ROTATE_270) - def transpose(first, second): - return im.transpose(first).transpose(second) + def test_roundtrip(self): - assert_image_equal(im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) - assert_image_equal(im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) + im = lena() - assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) - assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) + def transpose(first, second): + return im.transpose(first).transpose(second) + self.assert_image_equal( + im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) + self.assert_image_equal( + im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) + + self.assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) + self.assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 16eaaf55e..fe377f864 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -1,56 +1,74 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageChops -def test_sanity(): - im = lena("L") +class TestImageChops(PillowTestCase): - ImageChops.constant(im, 128) - ImageChops.duplicate(im) - ImageChops.invert(im) - ImageChops.lighter(im, im) - ImageChops.darker(im, im) - ImageChops.difference(im, im) - ImageChops.multiply(im, im) - ImageChops.screen(im, im) + def test_sanity(self): - ImageChops.add(im, im) - ImageChops.add(im, im, 2.0) - ImageChops.add(im, im, 2.0, 128) - ImageChops.subtract(im, im) - ImageChops.subtract(im, im, 2.0) - ImageChops.subtract(im, im, 2.0, 128) + im = lena("L") - ImageChops.add_modulo(im, im) - ImageChops.subtract_modulo(im, im) + ImageChops.constant(im, 128) + ImageChops.duplicate(im) + ImageChops.invert(im) + ImageChops.lighter(im, im) + ImageChops.darker(im, im) + ImageChops.difference(im, im) + ImageChops.multiply(im, im) + ImageChops.screen(im, im) - ImageChops.blend(im, im, 0.5) - ImageChops.composite(im, im, im) + ImageChops.add(im, im) + ImageChops.add(im, im, 2.0) + ImageChops.add(im, im, 2.0, 128) + ImageChops.subtract(im, im) + ImageChops.subtract(im, im, 2.0) + ImageChops.subtract(im, im, 2.0, 128) - ImageChops.offset(im, 10) - ImageChops.offset(im, 10, 20) + ImageChops.add_modulo(im, im) + ImageChops.subtract_modulo(im, im) -def test_logical(): + ImageChops.blend(im, im, 0.5) + ImageChops.composite(im, im, im) - def table(op, a, b): - out = [] - for x in (a, b): - imx = Image.new("1", (1, 1), x) - for y in (a, b): - imy = Image.new("1", (1, 1), y) - out.append(op(imx, imy).getpixel((0, 0))) - return tuple(out) + ImageChops.offset(im, 10) + ImageChops.offset(im, 10, 20) - assert_equal(table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) - assert_equal(table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) - assert_equal(table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) + def test_logical(self): - assert_equal(table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) - assert_equal(table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) - assert_equal(table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) + def table(op, a, b): + out = [] + for x in (a, b): + imx = Image.new("1", (1, 1), x) + for y in (a, b): + imy = Image.new("1", (1, 1), y) + out.append(op(imx, imy).getpixel((0, 0))) + return tuple(out) - assert_equal(table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) - assert_equal(table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) - assert_equal(table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) + self.assertEqual( + table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) + self.assertEqual( + table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) + self.assertEqual( + table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) + + self.assertEqual( + table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) + self.assertEqual( + table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) + self.assertEqual( + table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) + + self.assertEqual( + table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) + self.assertEqual( + table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) + self.assertEqual( + table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index f52101eb1..d4432d9be 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -1,203 +1,214 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image + try: from PIL import ImageCms ImageCms.core.profile_open -except ImportError: - skip() +except ImportError as v: + # Skipped via setUp() + pass + SRGB = "Tests/icc/sRGB.icm" -def test_sanity(): +class TestImageCms(PillowTestCase): - # basic smoke test. - # this mostly follows the cms_test outline. + def setUp(self): + try: + from PIL import ImageCms + except ImportError as v: + self.skipTest(v) - v = ImageCms.versions() # should return four strings - assert_equal(v[0], '1.0.0 pil') - assert_equal(list(map(type, v)), [str, str, str, str]) + def test_sanity(self): - # internal version number - assert_match(ImageCms.core.littlecms_version, "\d+\.\d+$") + # basic smoke test. + # this mostly follows the cms_test outline. - i = ImageCms.profileToProfile(lena(), SRGB, SRGB) - assert_image(i, "RGB", (128, 128)) + v = ImageCms.versions() # should return four strings + self.assertEqual(v[0], '1.0.0 pil') + self.assertEqual(list(map(type, v)), [str, str, str, str]) - i = lena() - ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) - assert_image(i, "RGB", (128, 128)) + # internal version number + self.assertRegexpMatches(ImageCms.core.littlecms_version, "\d+\.\d+$") - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "RGB", (128, 128)) + i = ImageCms.profileToProfile(lena(), SRGB, SRGB) + self.assert_image(i, "RGB", (128, 128)) - i = lena() - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - ImageCms.applyTransform(lena(), t, inPlace=True) - assert_image(i, "RGB", (128, 128)) + i = lena() + ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) + self.assert_image(i, "RGB", (128, 128)) - p = ImageCms.createProfile("sRGB") - o = ImageCms.getOpenProfile(SRGB) - t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "RGB", (128, 128)) + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "RGB", (128, 128)) - t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") - assert_equal(t.inputMode, "RGB") - assert_equal(t.outputMode, "RGB") - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "RGB", (128, 128)) + i = lena() + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + ImageCms.applyTransform(lena(), t, inPlace=True) + self.assert_image(i, "RGB", (128, 128)) - # test PointTransform convenience API - lena().point(t) + p = ImageCms.createProfile("sRGB") + o = ImageCms.getOpenProfile(SRGB) + t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "RGB", (128, 128)) + + t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") + self.assertEqual(t.inputMode, "RGB") + self.assertEqual(t.outputMode, "RGB") + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "RGB", (128, 128)) + + # test PointTransform convenience API + lena().point(t) + + def test_name(self): + # get profile information for file + self.assertEqual( + ImageCms.getProfileName(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_info(self): + self.assertEqual( + ImageCms.getProfileInfo(SRGB).splitlines(), [ + 'sRGB IEC61966-2.1', '', + 'Copyright (c) 1998 Hewlett-Packard Company', '']) + + def test_copyright(self): + self.assertEqual( + ImageCms.getProfileCopyright(SRGB).strip(), + 'Copyright (c) 1998 Hewlett-Packard Company') + + def test_manufacturer(self): + self.assertEqual( + ImageCms.getProfileManufacturer(SRGB).strip(), + 'IEC http://www.iec.ch') + + def test_model(self): + self.assertEqual( + ImageCms.getProfileModel(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_description(self): + self.assertEqual( + ImageCms.getProfileDescription(SRGB).strip(), + 'sRGB IEC61966-2.1') + + def test_intent(self): + self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0) + self.assertEqual(ImageCms.isIntentSupported( + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + + def test_profile_object(self): + # same, using profile object + p = ImageCms.createProfile("sRGB") + # self.assertEqual(ImageCms.getProfileName(p).strip(), + # 'sRGB built-in - (lcms internal)') + # self.assertEqual(ImageCms.getProfileInfo(p).splitlines(), + # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) + self.assertEqual(ImageCms.getDefaultIntent(p), 0) + self.assertEqual(ImageCms.isIntentSupported( + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + + def test_extensions(self): + # extensions + from io import BytesIO + i = Image.open("Tests/images/rgb.jpg") + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + self.assertEqual( + ImageCms.getProfileName(p).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_exceptions(self): + # the procedural pyCMS API uses PyCMSError for all sorts of errors + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.getProfileName(None)) + self.assertRaises( + ImageCms.PyCMSError, + lambda: ImageCms.isIntentSupported(SRGB, None, None)) + + def test_display_profile(self): + # try fetching the profile for the current display device + ImageCms.get_display_profile() + + def test_lab_color_profile(self): + ImageCms.createProfile("LAB", 5000) + ImageCms.createProfile("LAB", 6500) + + def test_simple_lab(self): + i = Image.new('RGB', (10, 10), (128, 128, 128)) + + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + + i_lab = ImageCms.applyTransform(i, t) + + self.assertEqual(i_lab.mode, 'LAB') + + k = i_lab.getpixel((0, 0)) + # not a linear luminance map. so L != 128: + self.assertEqual(k, (137, 128, 128)) + + L = i_lab.getdata(0) + a = i_lab.getdata(1) + b = i_lab.getdata(2) + + self.assertEqual(list(L), [137] * 100) + self.assertEqual(list(a), [128] * 100) + self.assertEqual(list(b), [128] * 100) + + def test_lab_color(self): + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + # Need to add a type mapping for some PIL type to TYPE_Lab_8 in + # findLCMSType, and have that mapping work back to a PIL mode + # (likely RGB). + i = ImageCms.applyTransform(lena(), t) + self.assert_image(i, "LAB", (128, 128)) + + # i.save('temp.lab.tif') # visually verified vs PS. + + target = Image.open('Tests/images/lena.Lab.tif') + + self.assert_image_similar(i, target, 30) + + def test_lab_srgb(self): + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") + + img = Image.open('Tests/images/lena.Lab.tif') + + img_srgb = ImageCms.applyTransform(img, t) + + # img_srgb.save('temp.srgb.tif') # visually verified vs ps. + + self.assert_image_similar(lena(), img_srgb, 30) + + def test_lab_roundtrip(self): + # check to see if we're at least internally consistent. + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + + t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") + + i = ImageCms.applyTransform(lena(), t) + out = ImageCms.applyTransform(i, t2) + + self.assert_image_similar(lena(), out, 2) -def test_name(): - # get profile information for file - assert_equal(ImageCms.getProfileName(SRGB).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') +if __name__ == '__main__': + unittest.main() - -def test_info(): - assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), - ['sRGB IEC61966-2.1', '', - 'Copyright (c) 1998 Hewlett-Packard Company', '']) - - -def test_copyright(): - assert_equal(ImageCms.getProfileCopyright(SRGB).strip(), - 'Copyright (c) 1998 Hewlett-Packard Company') - - -def test_manufacturer(): - assert_equal(ImageCms.getProfileManufacturer(SRGB).strip(), - 'IEC http://www.iec.ch') - - -def test_model(): - assert_equal(ImageCms.getProfileModel(SRGB).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - -def test_description(): - assert_equal(ImageCms.getProfileDescription(SRGB).strip(), - 'sRGB IEC61966-2.1') - - -def test_intent(): - assert_equal(ImageCms.getDefaultIntent(SRGB), 0) - assert_equal(ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) - - -def test_profile_object(): - # same, using profile object - p = ImageCms.createProfile("sRGB") -# assert_equal(ImageCms.getProfileName(p).strip(), -# 'sRGB built-in - (lcms internal)') -# assert_equal(ImageCms.getProfileInfo(p).splitlines(), -# ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) - assert_equal(ImageCms.getDefaultIntent(p), 0) - assert_equal(ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) - - -def test_extensions(): - # extensions - i = Image.open("Tests/images/rgb.jpg") - p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) - assert_equal(ImageCms.getProfileName(p).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - -def test_exceptions(): - # the procedural pyCMS API uses PyCMSError for all sorts of errors - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.getProfileName(None)) - assert_exception( - ImageCms.PyCMSError, - lambda: ImageCms.isIntentSupported(SRGB, None, None)) - - -def test_display_profile(): - # try fetching the profile for the current display device - assert_no_exception(lambda: ImageCms.get_display_profile()) - - -def test_lab_color_profile(): - ImageCms.createProfile("LAB", 5000) - ImageCms.createProfile("LAB", 6500) - - -def test_simple_lab(): - i = Image.new('RGB', (10, 10), (128, 128, 128)) - - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - - i_lab = ImageCms.applyTransform(i, t) - - assert_equal(i_lab.mode, 'LAB') - - k = i_lab.getpixel((0, 0)) - assert_equal(k, (137, 128, 128)) # not a linear luminance map. so L != 128 - - L = i_lab.getdata(0) - a = i_lab.getdata(1) - b = i_lab.getdata(2) - - assert_equal(list(L), [137] * 100) - assert_equal(list(a), [128] * 100) - assert_equal(list(b), [128] * 100) - - -def test_lab_color(): - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - # Need to add a type mapping for some PIL type to TYPE_Lab_8 in - # findLCMSType, and have that mapping work back to a PIL mode (likely RGB). - i = ImageCms.applyTransform(lena(), t) - assert_image(i, "LAB", (128, 128)) - - # i.save('temp.lab.tif') # visually verified vs PS. - - target = Image.open('Tests/images/lena.Lab.tif') - - assert_image_similar(i, target, 30) - - -def test_lab_srgb(): - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") - - img = Image.open('Tests/images/lena.Lab.tif') - - img_srgb = ImageCms.applyTransform(img, t) - - # img_srgb.save('temp.srgb.tif') # visually verified vs ps. - - assert_image_similar(lena(), img_srgb, 30) - - -def test_lab_roundtrip(): - # check to see if we're at least internally consistent. - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - - t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") - - i = ImageCms.applyTransform(lena(), t) - out = ImageCms.applyTransform(i, t2) - - assert_image_similar(lena(), out, 2) +# End of file diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index c67c20255..fce64876b 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,54 +1,71 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image from PIL import ImageColor -# -------------------------------------------------------------------- -# sanity -assert_equal((255, 0, 0), ImageColor.getrgb("#f00")) -assert_equal((255, 0, 0), ImageColor.getrgb("#ff0000")) -assert_equal((255, 0, 0), ImageColor.getrgb("rgb(255,0,0)")) -assert_equal((255, 0, 0), ImageColor.getrgb("rgb(255, 0, 0)")) -assert_equal((255, 0, 0), ImageColor.getrgb("rgb(100%,0%,0%)")) -assert_equal((255, 0, 0), ImageColor.getrgb("hsl(0, 100%, 50%)")) -assert_equal((255, 0, 0, 0), ImageColor.getrgb("rgba(255,0,0,0)")) -assert_equal((255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) -assert_equal((255, 0, 0), ImageColor.getrgb("red")) +class TestImageColor(PillowTestCase): -# -------------------------------------------------------------------- -# look for rounding errors (based on code by Tim Hatch) + def test_sanity(self): + self.assertEqual((255, 0, 0), ImageColor.getrgb("#f00")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("#ff0000")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(255,0,0)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(255, 0, 0)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(100%,0%,0%)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(0, 100%, 50%)")) + self.assertEqual((255, 0, 0, 0), ImageColor.getrgb("rgba(255,0,0,0)")) + self.assertEqual( + (255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) + self.assertEqual((255, 0, 0), ImageColor.getrgb("red")) -for color in list(ImageColor.colormap.keys()): - expected = Image.new("RGB", (1, 1), color).convert("L").getpixel((0, 0)) - actual = Image.new("L", (1, 1), color).getpixel((0, 0)) - assert_equal(expected, actual) + # look for rounding errors (based on code by Tim Hatch) + def test_rounding_errors(self): -assert_equal((0, 0, 0), ImageColor.getcolor("black", "RGB")) -assert_equal((255, 255, 255), ImageColor.getcolor("white", "RGB")) -assert_equal((0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) -Image.new("RGB", (1, 1), "white") + for color in list(ImageColor.colormap.keys()): + expected = Image.new( + "RGB", (1, 1), color).convert("L").getpixel((0, 0)) + actual = Image.new("L", (1, 1), color).getpixel((0, 0)) + self.assertEqual(expected, actual) -assert_equal((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) -assert_equal((255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) -assert_equal((0, 255, 115, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) -Image.new("RGBA", (1, 1), "white") + self.assertEqual((0, 0, 0), ImageColor.getcolor("black", "RGB")) + self.assertEqual((255, 255, 255), ImageColor.getcolor("white", "RGB")) + self.assertEqual( + (0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) + Image.new("RGB", (1, 1), "white") -assert_equal(0, ImageColor.getcolor("black", "L")) -assert_equal(255, ImageColor.getcolor("white", "L")) -assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) -Image.new("L", (1, 1), "white") + self.assertEqual((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) + self.assertEqual( + (255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) + self.assertEqual( + (0, 255, 115, 33), + ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) + Image.new("RGBA", (1, 1), "white") -assert_equal(0, ImageColor.getcolor("black", "1")) -assert_equal(255, ImageColor.getcolor("white", "1")) -# The following test is wrong, but is current behavior -# The correct result should be 255 due to the mode 1 -assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) -# Correct behavior -# assert_equal(255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) -Image.new("1", (1, 1), "white") + self.assertEqual(0, ImageColor.getcolor("black", "L")) + self.assertEqual(255, ImageColor.getcolor("white", "L")) + self.assertEqual( + 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) + Image.new("L", (1, 1), "white") -assert_equal((0, 255), ImageColor.getcolor("black", "LA")) -assert_equal((255, 255), ImageColor.getcolor("white", "LA")) -assert_equal((162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) -Image.new("LA", (1, 1), "white") + self.assertEqual(0, ImageColor.getcolor("black", "1")) + self.assertEqual(255, ImageColor.getcolor("white", "1")) + # The following test is wrong, but is current behavior + # The correct result should be 255 due to the mode 1 + self.assertEqual( + 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) + # Correct behavior + # self.assertEqual( + # 255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) + Image.new("1", (1, 1), "white") + + self.assertEqual((0, 255), ImageColor.getcolor("black", "LA")) + self.assertEqual((255, 255), ImageColor.getcolor("white", "LA")) + self.assertEqual( + (162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) + Image.new("LA", (1, 1), "white") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 7b682020e..a19ba78f8 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,9 +1,11 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageColor from PIL import ImageDraw +import sys + # Image size w, h = 100, 100 @@ -22,249 +24,232 @@ points1 = [(10, 10), (20, 40), (30, 30)] points2 = [10, 10, 20, 40, 30, 30] -def test_sanity(): +class TestImageDraw(PillowTestCase): - im = lena("RGB").copy() + def test_sanity(self): + im = lena("RGB").copy() - draw = ImageDraw.ImageDraw(im) - draw = ImageDraw.Draw(im) + draw = ImageDraw.ImageDraw(im) + draw = ImageDraw.Draw(im) - draw.ellipse(list(range(4))) - draw.line(list(range(10))) - draw.polygon(list(range(100))) - draw.rectangle(list(range(4))) + draw.ellipse(list(range(4))) + draw.line(list(range(10))) + draw.polygon(list(range(100))) + draw.rectangle(list(range(4))) - success() + def test_deprecated(self): + im = lena().copy() + draw = ImageDraw.Draw(im) -def test_deprecated(): + self.assert_warning(DeprecationWarning, lambda: draw.setink(0)) + self.assert_warning(DeprecationWarning, lambda: draw.setfill(0)) - im = lena().copy() + def helper_arc(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) - draw = ImageDraw.Draw(im) + # Act + # FIXME Fill param should be named outline. + draw.arc(bbox, 0, 180) + del draw - assert_warning(DeprecationWarning, lambda: draw.setink(0)) - assert_warning(DeprecationWarning, lambda: draw.setfill(0)) + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_arc.png")) + def test_arc1(self): + self.helper_arc(bbox1) -def helper_arc(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + def test_arc2(self): + self.helper_arc(bbox2) - # Act - # FIXME Fill param should be named outline. - draw.arc(bbox, 0, 180) - del draw + def test_bitmap(self): + # Arrange + small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_arc.png")) + # Act + draw.bitmap((10, 10), small) + del draw + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_bitmap.png")) -def test_arc1(): - helper_arc(bbox1) + def helper_chord(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + # Act + draw.chord(bbox, 0, 180, fill="red", outline="yellow") + del draw -def test_arc2(): - helper_arc(bbox2) + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_chord.png")) + def test_chord1(self): + self.helper_chord(bbox1) -def test_bitmap(): - # Arrange - small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + def test_chord2(self): + self.helper_chord(bbox2) - # Act - draw.bitmap((10, 10), small) - del draw + def helper_ellipse(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) + # Act + draw.ellipse(bbox, fill="green", outline="blue") + del draw + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_ellipse.png")) -def helper_chord(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + def test_ellipse1(self): + self.helper_ellipse(bbox1) - # Act - draw.chord(bbox, 0, 180, fill="red", outline="yellow") - del draw + def test_ellipse2(self): + self.helper_ellipse(bbox2) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_chord.png")) + def helper_line(self, points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + # Act + draw.line(points1, fill="yellow", width=2) + del draw -def test_chord1(): - helper_chord(bbox1) + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_line.png")) + def test_line1(self): + self.helper_line(points1) -def test_chord2(): - helper_chord(bbox2) + def test_line2(self): + self.helper_line(points2) + def helper_pieslice(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) -def helper_ellipse(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + # Act + draw.pieslice(bbox, -90, 45, fill="white", outline="blue") + del draw - # Act - draw.ellipse(bbox, fill="green", outline="blue") - del draw + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_pieslice.png")) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_ellipse.png")) + def test_pieslice1(self): + self.helper_pieslice(bbox1) + def test_pieslice2(self): + self.helper_pieslice(bbox2) -def test_ellipse1(): - helper_ellipse(bbox1) + def helper_point(self, points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + # Act + draw.point(points1, fill="yellow") + del draw -def test_ellipse2(): - helper_ellipse(bbox2) + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_point.png")) + def test_point1(self): + self.helper_point(points1) -def helper_line(points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + def test_point2(self): + self.helper_point(points2) - # Act - draw.line(points1, fill="yellow", width=2) - del draw + def helper_polygon(self, points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + # Act + draw.polygon(points1, fill="red", outline="blue") + del draw + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_polygon.png")) -def test_line1(): - helper_line(points1) + def test_polygon1(self): + self.helper_polygon(points1) + def test_polygon2(self): + self.helper_polygon(points2) -def test_line2(): - helper_line(points2) + def helper_rectangle(self, bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + # Act + draw.rectangle(bbox, fill="black", outline="green") + del draw -def helper_pieslice(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_rectangle.png")) - # Act - draw.pieslice(bbox, -90, 45, fill="white", outline="blue") - del draw + def test_rectangle1(self): + self.helper_rectangle(bbox1) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) + def test_rectangle2(self): + self.helper_rectangle(bbox2) + def test_floodfill(self): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) -def test_pieslice1(): - helper_pieslice(bbox1) + # Act + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) + del draw + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_floodfill.png")) -def test_pieslice2(): - helper_pieslice(bbox2) + @unittest.skipIf(hasattr(sys, 'pypy_version_info'), + "Causes fatal RPython error on PyPy") + def test_floodfill_border(self): + # floodfill() is experimental + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) -def helper_point(points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) + # Act + ImageDraw.floodfill( + im, centre_point, ImageColor.getrgb("red"), + border=ImageColor.getrgb("black")) + del draw - # Act - draw.point(points1, fill="yellow") - del draw + # Assert + self.assert_image_equal( + im, Image.open("Tests/images/imagedraw_floodfill2.png")) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) - - -def test_point1(): - helper_point(points1) - - -def test_point2(): - helper_point(points2) - - -def helper_polygon(points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.polygon(points1, fill="red", outline="blue") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) - - -def test_polygon1(): - helper_polygon(points1) - - -def test_polygon2(): - helper_polygon(points2) - - -def helper_rectangle(bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.rectangle(bbox, fill="black", outline="green") - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) - - -def test_rectangle1(): - helper_rectangle(bbox1) - - -def test_rectangle2(): - helper_rectangle(bbox2) - - -def test_floodfill(): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) - - # Act - ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) - - -def test_floodfill_border(): - # floodfill() is experimental - if hasattr(sys, 'pypy_version_info'): - # Causes fatal RPython error on PyPy - skip() - - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) - - # Act - ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), - border=ImageColor.getrgb("black")) - del draw - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) +if __name__ == '__main__': + unittest.main() # End of file diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 04f16bfa5..eec26d768 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -1,19 +1,28 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageEnhance -def test_sanity(): - # FIXME: assert_image - assert_no_exception(lambda: ImageEnhance.Color(lena()).enhance(0.5)) - assert_no_exception(lambda: ImageEnhance.Contrast(lena()).enhance(0.5)) - assert_no_exception(lambda: ImageEnhance.Brightness(lena()).enhance(0.5)) - assert_no_exception(lambda: ImageEnhance.Sharpness(lena()).enhance(0.5)) +class TestImageEnhance(PillowTestCase): -def test_crash(): + def test_sanity(self): - # crashes on small images - im = Image.new("RGB", (1, 1)) - assert_no_exception(lambda: ImageEnhance.Sharpness(im).enhance(0.5)) + # FIXME: assert_image + # Implicit asserts no exception: + ImageEnhance.Color(lena()).enhance(0.5) + ImageEnhance.Contrast(lena()).enhance(0.5) + ImageEnhance.Brightness(lena()).enhance(0.5) + ImageEnhance.Sharpness(lena()).enhance(0.5) + def test_crash(self): + + # crashes on small images + im = Image.new("RGB", (1, 1)) + ImageEnhance.Sharpness(im).enhance(0.5) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index e3992828a..849767195 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,82 +1,93 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena, fromstring, tostring + +from io import BytesIO from PIL import Image from PIL import ImageFile from PIL import EpsImagePlugin + codecs = dir(Image.core) # save original block sizes MAXBLOCK = ImageFile.MAXBLOCK SAFEBLOCK = ImageFile.SAFEBLOCK -def test_parser(): - def roundtrip(format): +class TestImagePutData(PillowTestCase): - im = lena("L").resize((1000, 1000)) - if format in ("MSP", "XBM"): - im = im.convert("1") + def test_parser(self): - file = BytesIO() + def roundtrip(format): - im.save(file, format) + im = lena("L").resize((1000, 1000)) + if format in ("MSP", "XBM"): + im = im.convert("1") - data = file.getvalue() + file = BytesIO() - parser = ImageFile.Parser() - parser.feed(data) - imOut = parser.close() + im.save(file, format) - return im, imOut + data = file.getvalue() + + parser = ImageFile.Parser() + parser.feed(data) + imOut = parser.close() + + return im, imOut + + self.assert_image_equal(*roundtrip("BMP")) + self.assert_image_equal(*roundtrip("GIF")) + self.assert_image_equal(*roundtrip("IM")) + self.assert_image_equal(*roundtrip("MSP")) + if "zip_encoder" in codecs: + try: + # force multiple blocks in PNG driver + ImageFile.MAXBLOCK = 8192 + self.assert_image_equal(*roundtrip("PNG")) + finally: + ImageFile.MAXBLOCK = MAXBLOCK + self.assert_image_equal(*roundtrip("PPM")) + self.assert_image_equal(*roundtrip("TIFF")) + self.assert_image_equal(*roundtrip("XBM")) + self.assert_image_equal(*roundtrip("TGA")) + self.assert_image_equal(*roundtrip("PCX")) + + if EpsImagePlugin.has_ghostscript(): + im1, im2 = roundtrip("EPS") + # EPS comes back in RGB: + self.assert_image_similar(im1, im2.convert('L'), 20) + + if "jpeg_encoder" in codecs: + im1, im2 = roundtrip("JPEG") # lossy compression + self.assert_image(im1, im2.mode, im2.size) + + self.assertRaises(IOError, lambda: roundtrip("PDF")) + + def test_ico(self): + with open('Tests/images/python.ico', 'rb') as f: + data = f.read() + p = ImageFile.Parser() + p.feed(data) + self.assertEqual((48, 48), p.image.size) + + def test_safeblock(self): + + im1 = lena() + + if "zip_encoder" not in codecs: + self.skipTest("PNG (zlib) encoder not available") - assert_image_equal(*roundtrip("BMP")) - assert_image_equal(*roundtrip("GIF")) - assert_image_equal(*roundtrip("IM")) - assert_image_equal(*roundtrip("MSP")) - if "zip_encoder" in codecs: try: - # force multiple blocks in PNG driver - ImageFile.MAXBLOCK = 8192 - assert_image_equal(*roundtrip("PNG")) + ImageFile.SAFEBLOCK = 1 + im2 = fromstring(tostring(im1, "PNG")) finally: - ImageFile.MAXBLOCK = MAXBLOCK - assert_image_equal(*roundtrip("PPM")) - assert_image_equal(*roundtrip("TIFF")) - assert_image_equal(*roundtrip("XBM")) - assert_image_equal(*roundtrip("TGA")) - assert_image_equal(*roundtrip("PCX")) + ImageFile.SAFEBLOCK = SAFEBLOCK - if EpsImagePlugin.has_ghostscript(): - im1, im2 = roundtrip("EPS") - assert_image_similar(im1, im2.convert('L'),20) # EPS comes back in RGB + self.assert_image_equal(im1, im2) - if "jpeg_encoder" in codecs: - im1, im2 = roundtrip("JPEG") # lossy compression - assert_image(im1, im2.mode, im2.size) - # XXX Why assert exception and why does it fail? - # https://github.com/python-pillow/Pillow/issues/78 - #assert_exception(IOError, lambda: roundtrip("PDF")) +if __name__ == '__main__': + unittest.main() -def test_ico(): - with open('Tests/images/python.ico', 'rb') as f: - data = f.read() - p = ImageFile.Parser() - p.feed(data) - assert_equal((48,48), p.image.size) - -def test_safeblock(): - - im1 = lena() - - if "zip_encoder" not in codecs: - skip("PNG (zlib) encoder not available") - - try: - ImageFile.SAFEBLOCK = 1 - im2 = fromstring(tostring(im1, "PNG")) - finally: - ImageFile.SAFEBLOCK = SAFEBLOCK - - assert_image_equal(im1, im2) +# End of file diff --git a/Tests/test_imagefileio.py b/Tests/test_imagefileio.py index c63f07bb0..791207dca 100644 --- a/Tests/test_imagefileio.py +++ b/Tests/test_imagefileio.py @@ -1,24 +1,33 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena, tostring from PIL import Image from PIL import ImageFileIO -def test_fileio(): - class DumbFile: - def __init__(self, data): - self.data = data - def read(self, bytes=None): - assert_equal(bytes, None) - return self.data - def close(self): - pass +class TestImageFileIo(PillowTestCase): - im1 = lena() + def test_fileio(self): - io = ImageFileIO.ImageFileIO(DumbFile(tostring(im1, "PPM"))) + class DumbFile: + def __init__(self, data): + self.data = data - im2 = Image.open(io) - assert_image_equal(im1, im2) + def read(self, bytes=None): + assert(bytes is None) + return self.data + + def close(self): + pass + + im1 = lena() + + io = ImageFileIO.ImageFileIO(DumbFile(tostring(im1, "PPM"))) + + im2 = Image.open(io) + self.assert_image_equal(im1, im2) +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagefilter.py b/Tests/test_imagefilter.py index 214f88024..3dcb1d14f 100644 --- a/Tests/test_imagefilter.py +++ b/Tests/test_imagefilter.py @@ -1,31 +1,37 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image from PIL import ImageFilter -def test_sanity(): - # see test_image_filter for more tests - assert_no_exception(lambda: ImageFilter.MaxFilter) - assert_no_exception(lambda: ImageFilter.MedianFilter) - assert_no_exception(lambda: ImageFilter.MinFilter) - assert_no_exception(lambda: ImageFilter.ModeFilter) - assert_no_exception(lambda: ImageFilter.Kernel((3, 3), list(range(9)))) - assert_no_exception(lambda: ImageFilter.GaussianBlur) - assert_no_exception(lambda: ImageFilter.GaussianBlur(5)) - assert_no_exception(lambda: ImageFilter.UnsharpMask) - assert_no_exception(lambda: ImageFilter.UnsharpMask(10)) +class TestImageFilter(PillowTestCase): - assert_no_exception(lambda: ImageFilter.BLUR) - assert_no_exception(lambda: ImageFilter.CONTOUR) - assert_no_exception(lambda: ImageFilter.DETAIL) - assert_no_exception(lambda: ImageFilter.EDGE_ENHANCE) - assert_no_exception(lambda: ImageFilter.EDGE_ENHANCE_MORE) - assert_no_exception(lambda: ImageFilter.EMBOSS) - assert_no_exception(lambda: ImageFilter.FIND_EDGES) - assert_no_exception(lambda: ImageFilter.SMOOTH) - assert_no_exception(lambda: ImageFilter.SMOOTH_MORE) - assert_no_exception(lambda: ImageFilter.SHARPEN) + def test_sanity(self): + # see test_image_filter for more tests + + # Check these run. Exceptions cause failures. + ImageFilter.MaxFilter + ImageFilter.MedianFilter + ImageFilter.MinFilter + ImageFilter.ModeFilter + ImageFilter.Kernel((3, 3), list(range(9))) + ImageFilter.GaussianBlur + ImageFilter.GaussianBlur(5) + ImageFilter.UnsharpMask + ImageFilter.UnsharpMask(10) + + ImageFilter.BLUR + ImageFilter.CONTOUR + ImageFilter.DETAIL + ImageFilter.EDGE_ENHANCE + ImageFilter.EDGE_ENHANCE_MORE + ImageFilter.EMBOSS + ImageFilter.FIND_EDGES + ImageFilter.SMOOTH + ImageFilter.SMOOTH_MORE + ImageFilter.SHARPEN +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 9ac2cdd89..927c80bee 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1,135 +1,145 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image +from PIL import ImageDraw from io import BytesIO import os +font_path = "Tests/fonts/FreeMono.ttf" +font_size = 20 + + try: from PIL import ImageFont - ImageFont.core.getfont # check if freetype is available + ImageFont.core.getfont # check if freetype is available + + class TestImageFont(PillowTestCase): + + def test_sanity(self): + self.assertRegexpMatches( + ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") + + def test_font_with_name(self): + ImageFont.truetype(font_path, font_size) + self._render(font_path) + self._clean() + + def _font_as_bytes(self): + with open(font_path, 'rb') as f: + font_bytes = BytesIO(f.read()) + return font_bytes + + def test_font_with_filelike(self): + ImageFont.truetype(self._font_as_bytes(), font_size) + self._render(self._font_as_bytes()) + # Usage note: making two fonts from the same buffer fails. + # shared_bytes = self._font_as_bytes() + # self._render(shared_bytes) + # self.assertRaises(Exception, lambda: _render(shared_bytes)) + self._clean() + + def test_font_with_open_file(self): + with open(font_path, 'rb') as f: + self._render(f) + self._clean() + + def test_font_old_parameters(self): + self.assert_warning( + DeprecationWarning, + lambda: ImageFont.truetype(filename=font_path, size=font_size)) + + def _render(self, font): + txt = "Hello World!" + ttf = ImageFont.truetype(font, font_size) + w, h = ttf.getsize(txt) + img = Image.new("RGB", (256, 64), "white") + d = ImageDraw.Draw(img) + d.text((10, 10), txt, font=ttf, fill='black') + + img.save('font.png') + return img + + def _clean(self): + os.unlink('font.png') + + def test_render_equal(self): + img_path = self._render(font_path) + with open(font_path, 'rb') as f: + font_filelike = BytesIO(f.read()) + img_filelike = self._render(font_filelike) + + self.assert_image_equal(img_path, img_filelike) + self._clean() + + def test_render_multiline(self): + im = Image.new(mode='RGB', size=(300, 100)) + draw = ImageDraw.Draw(im) + ttf = ImageFont.truetype(font_path, font_size) + line_spacing = draw.textsize('A', font=ttf)[1] + 8 + lines = ['hey you', 'you are awesome', 'this looks awkward'] + y = 0 + for line in lines: + draw.text((0, y), line, font=ttf) + y += line_spacing + + target = 'Tests/images/multiline_text.png' + target_img = Image.open(target) + + # some versions of freetype have different horizontal spacing. + # setting a tight epsilon, I'm showing the original test failure + # at epsilon = ~38. + self.assert_image_similar(im, target_img, .5) + + def test_rotated_transposed_font(self): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" + font = ImageFont.truetype(font_path, font_size) + + orientation = Image.ROTATE_90 + transposed_font = ImageFont.TransposedFont( + font, orientation=orientation) + + # Original font + draw.setfont(font) + box_size_a = draw.textsize(word) + + # Rotated font + draw.setfont(transposed_font) + box_size_b = draw.textsize(word) + + # Check (w,h) of box a is (h,w) of box b + self.assertEqual(box_size_a[0], box_size_b[1]) + self.assertEqual(box_size_a[1], box_size_b[0]) + + def test_unrotated_transposed_font(self): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" + font = ImageFont.truetype(font_path, font_size) + + orientation = None + transposed_font = ImageFont.TransposedFont( + font, orientation=orientation) + + # Original font + draw.setfont(font) + box_size_a = draw.textsize(word) + + # Rotated font + draw.setfont(transposed_font) + box_size_b = draw.textsize(word) + + # Check boxes a and b are same size + self.assertEqual(box_size_a, box_size_b) + except ImportError: - skip() - -from PIL import ImageDraw - -font_path = "Tests/fonts/FreeMono.ttf" -font_size=20 - -def test_sanity(): - assert_match(ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") - -def test_font_with_name(): - assert_no_exception(lambda: ImageFont.truetype(font_path, font_size)) - assert_no_exception(lambda: _render(font_path)) - _clean() - -def _font_as_bytes(): - with open(font_path, 'rb') as f: - font_bytes = BytesIO(f.read()) - return font_bytes - -def test_font_with_filelike(): - assert_no_exception(lambda: ImageFont.truetype(_font_as_bytes(), font_size)) - assert_no_exception(lambda: _render(_font_as_bytes())) - # Usage note: making two fonts from the same buffer fails. - #shared_bytes = _font_as_bytes() - #assert_no_exception(lambda: _render(shared_bytes)) - #assert_exception(Exception, lambda: _render(shared_bytes)) - _clean() - -def test_font_with_open_file(): - with open(font_path, 'rb') as f: - assert_no_exception(lambda: _render(f)) - _clean() - -def test_font_old_parameters(): - assert_warning(DeprecationWarning, lambda: ImageFont.truetype(filename=font_path, size=font_size)) - -def _render(font): - txt = "Hello World!" - ttf = ImageFont.truetype(font, font_size) - w, h = ttf.getsize(txt) - img = Image.new("RGB", (256, 64), "white") - d = ImageDraw.Draw(img) - d.text((10, 10), txt, font=ttf, fill='black') - - img.save('font.png') - return img - -def _clean(): - os.unlink('font.png') - -def test_render_equal(): - img_path = _render(font_path) - with open(font_path, 'rb') as f: - font_filelike = BytesIO(f.read()) - img_filelike = _render(font_filelike) - - assert_image_equal(img_path, img_filelike) - _clean() + class TestImageFont(PillowTestCase): + def test_skip(self): + self.skipTest("ImportError") -def test_render_multiline(): - im = Image.new(mode='RGB', size=(300,100)) - draw = ImageDraw.Draw(im) - ttf = ImageFont.truetype(font_path, font_size) - line_spacing = draw.textsize('A', font=ttf)[1] + 8 - lines = ['hey you', 'you are awesome', 'this looks awkward'] - y = 0 - for line in lines: - draw.text((0, y), line, font=ttf) - y += line_spacing - - - target = 'Tests/images/multiline_text.png' - target_img = Image.open(target) - - # some versions of freetype have different horizontal spacing. - # setting a tight epsilon, I'm showing the original test failure - # at epsilon = ~38. - assert_image_similar(im, target_img,.5) - - -def test_rotated_transposed_font(): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = ImageFont.truetype(font_path, font_size) - - orientation = Image.ROTATE_90 - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - - # Original font - draw.setfont(font) - box_size_a = draw.textsize(word) - - # Rotated font - draw.setfont(transposed_font) - box_size_b = draw.textsize(word) - - # Check (w,h) of box a is (h,w) of box b - assert_equal(box_size_a[0], box_size_b[1]) - assert_equal(box_size_a[1], box_size_b[0]) - - -def test_unrotated_transposed_font(): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = ImageFont.truetype(font_path, font_size) - - orientation = None - transposed_font = ImageFont.TransposedFont(font, orientation=orientation) - - # Original font - draw.setfont(font) - box_size_a = draw.textsize(word) - - # Rotated font - draw.setfont(transposed_font) - box_size_b = draw.textsize(word) - - # Check boxes a and b are same size - assert_equal(box_size_a, box_size_b) - +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 67ff71960..a6c50fb31 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -1,13 +1,25 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image try: from PIL import ImageGrab -except ImportError as v: - skip(v) -def test_grab(): - im = ImageGrab.grab() - assert_image(im, im.mode, im.size) + class TestImageCopy(PillowTestCase): + + def test_grab(self): + im = ImageGrab.grab() + self.assert_image(im, im.mode, im.size) + + def test_grab2(self): + im = ImageGrab.grab() + self.assert_image(im, im.mode, im.size) + +except ImportError: + class TestImageCopy(PillowTestCase): + def test_skip(self): + self.skipTest("ImportError") +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index eaeb711ba..35d75dbbd 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -1,14 +1,15 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image from PIL import ImageMath + def pixel(im): if hasattr(im, "im"): return "%s %r" % (im.mode, im.getpixel((0, 0))) else: if isinstance(im, type(0)): - return int(im) # hack to deal with booleans + return int(im) # hack to deal with booleans print(im) A = Image.new("L", (1, 1), 1) @@ -18,45 +19,60 @@ I = Image.new("I", (1, 1), 4) images = {"A": A, "B": B, "F": F, "I": I} -def test_sanity(): - assert_equal(ImageMath.eval("1"), 1) - assert_equal(ImageMath.eval("1+A", A=2), 3) - assert_equal(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") - assert_equal(pixel(ImageMath.eval("A+B", images)), "I 3") - assert_equal(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - assert_equal(pixel(ImageMath.eval("int(float(A)+B)", images)), "I 3") -def test_ops(): +class TestImageMath(PillowTestCase): - assert_equal(pixel(ImageMath.eval("-A", images)), "I -1") - assert_equal(pixel(ImageMath.eval("+B", images)), "L 2") + def test_sanity(self): + self.assertEqual(ImageMath.eval("1"), 1) + self.assertEqual(ImageMath.eval("1+A", A=2), 3) + self.assertEqual(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") + self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") + self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") + self.assertEqual(pixel( + ImageMath.eval("int(float(A)+B)", images)), "I 3") - assert_equal(pixel(ImageMath.eval("A+B", images)), "I 3") - assert_equal(pixel(ImageMath.eval("A-B", images)), "I -1") - assert_equal(pixel(ImageMath.eval("A*B", images)), "I 2") - assert_equal(pixel(ImageMath.eval("A/B", images)), "I 0") - assert_equal(pixel(ImageMath.eval("B**2", images)), "I 4") - assert_equal(pixel(ImageMath.eval("B**33", images)), "I 2147483647") + def test_ops(self): - assert_equal(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - assert_equal(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") - assert_equal(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") - assert_equal(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") - assert_equal(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") - assert_equal(pixel(ImageMath.eval("float(B)**33", images)), "F 8589934592.0") + self.assertEqual(pixel(ImageMath.eval("-A", images)), "I -1") + self.assertEqual(pixel(ImageMath.eval("+B", images)), "L 2") -def test_logical(): - assert_equal(pixel(ImageMath.eval("not A", images)), 0) - assert_equal(pixel(ImageMath.eval("A and B", images)), "L 2") - assert_equal(pixel(ImageMath.eval("A or B", images)), "L 1") + self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") + self.assertEqual(pixel(ImageMath.eval("A-B", images)), "I -1") + self.assertEqual(pixel(ImageMath.eval("A*B", images)), "I 2") + self.assertEqual(pixel(ImageMath.eval("A/B", images)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B**2", images)), "I 4") + self.assertEqual(pixel( + ImageMath.eval("B**33", images)), "I 2147483647") -def test_convert(): - assert_equal(pixel(ImageMath.eval("convert(A+B, 'L')", images)), "L 3") - assert_equal(pixel(ImageMath.eval("convert(A+B, '1')", images)), "1 0") - assert_equal(pixel(ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") + self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") + self.assertEqual(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") + self.assertEqual(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") + self.assertEqual(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") + self.assertEqual(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") + self.assertEqual(pixel( + ImageMath.eval("float(B)**33", images)), "F 8589934592.0") -def test_compare(): - assert_equal(pixel(ImageMath.eval("min(A, B)", images)), "I 1") - assert_equal(pixel(ImageMath.eval("max(A, B)", images)), "I 2") - assert_equal(pixel(ImageMath.eval("A == 1", images)), "I 1") - assert_equal(pixel(ImageMath.eval("A == 2", images)), "I 0") + def test_logical(self): + self.assertEqual(pixel(ImageMath.eval("not A", images)), 0) + self.assertEqual(pixel(ImageMath.eval("A and B", images)), "L 2") + self.assertEqual(pixel(ImageMath.eval("A or B", images)), "L 1") + + def test_convert(self): + self.assertEqual(pixel( + ImageMath.eval("convert(A+B, 'L')", images)), "L 3") + self.assertEqual(pixel( + ImageMath.eval("convert(A+B, '1')", images)), "1 0") + self.assertEqual(pixel( + ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") + + def test_compare(self): + self.assertEqual(pixel(ImageMath.eval("min(A, B)", images)), "I 1") + self.assertEqual(pixel(ImageMath.eval("max(A, B)", images)), "I 2") + self.assertEqual(pixel(ImageMath.eval("A == 1", images)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A == 2", images)), "I 0") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagemode.py b/Tests/test_imagemode.py index 54b04435f..7febc697e 100644 --- a/Tests/test_imagemode.py +++ b/Tests/test_imagemode.py @@ -1,23 +1,32 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image from PIL import ImageMode -ImageMode.getmode("1") -ImageMode.getmode("L") -ImageMode.getmode("P") -ImageMode.getmode("RGB") -ImageMode.getmode("I") -ImageMode.getmode("F") -m = ImageMode.getmode("1") -assert_equal(m.mode, "1") -assert_equal(m.bands, ("1",)) -assert_equal(m.basemode, "L") -assert_equal(m.basetype, "L") +class TestImageMode(PillowTestCase): -m = ImageMode.getmode("RGB") -assert_equal(m.mode, "RGB") -assert_equal(m.bands, ("R", "G", "B")) -assert_equal(m.basemode, "RGB") -assert_equal(m.basetype, "L") + def test_sanity(self): + ImageMode.getmode("1") + ImageMode.getmode("L") + ImageMode.getmode("P") + ImageMode.getmode("RGB") + ImageMode.getmode("I") + ImageMode.getmode("F") + + m = ImageMode.getmode("1") + self.assertEqual(m.mode, "1") + self.assertEqual(m.bands, ("1",)) + self.assertEqual(m.basemode, "L") + self.assertEqual(m.basetype, "L") + + m = ImageMode.getmode("RGB") + self.assertEqual(m.mode, "RGB") + self.assertEqual(m.bands, ("R", "G", "B")) + self.assertEqual(m.basemode, "RGB") + self.assertEqual(m.basetype, "L") + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 8ed5ccefa..299a7c618 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,81 +1,85 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena -from PIL import Image from PIL import ImageOps -class Deformer: - def getmesh(self, im): - x, y = im.size - return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] -deformer = Deformer() +class TestImageOps(PillowTestCase): -def test_sanity(): + class Deformer: + def getmesh(self, im): + x, y = im.size + return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] - ImageOps.autocontrast(lena("L")) - ImageOps.autocontrast(lena("RGB")) + deformer = Deformer() - ImageOps.autocontrast(lena("L"), cutoff=10) - ImageOps.autocontrast(lena("L"), ignore=[0, 255]) + def test_sanity(self): - ImageOps.colorize(lena("L"), (0, 0, 0), (255, 255, 255)) - ImageOps.colorize(lena("L"), "black", "white") + ImageOps.autocontrast(lena("L")) + ImageOps.autocontrast(lena("RGB")) - ImageOps.crop(lena("L"), 1) - ImageOps.crop(lena("RGB"), 1) + ImageOps.autocontrast(lena("L"), cutoff=10) + ImageOps.autocontrast(lena("L"), ignore=[0, 255]) - ImageOps.deform(lena("L"), deformer) - ImageOps.deform(lena("RGB"), deformer) + ImageOps.colorize(lena("L"), (0, 0, 0), (255, 255, 255)) + ImageOps.colorize(lena("L"), "black", "white") - ImageOps.equalize(lena("L")) - ImageOps.equalize(lena("RGB")) + ImageOps.crop(lena("L"), 1) + ImageOps.crop(lena("RGB"), 1) - ImageOps.expand(lena("L"), 1) - ImageOps.expand(lena("RGB"), 1) - ImageOps.expand(lena("L"), 2, "blue") - ImageOps.expand(lena("RGB"), 2, "blue") + ImageOps.deform(lena("L"), self.deformer) + ImageOps.deform(lena("RGB"), self.deformer) - ImageOps.fit(lena("L"), (128, 128)) - ImageOps.fit(lena("RGB"), (128, 128)) + ImageOps.equalize(lena("L")) + ImageOps.equalize(lena("RGB")) - ImageOps.flip(lena("L")) - ImageOps.flip(lena("RGB")) + ImageOps.expand(lena("L"), 1) + ImageOps.expand(lena("RGB"), 1) + ImageOps.expand(lena("L"), 2, "blue") + ImageOps.expand(lena("RGB"), 2, "blue") - ImageOps.grayscale(lena("L")) - ImageOps.grayscale(lena("RGB")) + ImageOps.fit(lena("L"), (128, 128)) + ImageOps.fit(lena("RGB"), (128, 128)) - ImageOps.invert(lena("L")) - ImageOps.invert(lena("RGB")) + ImageOps.flip(lena("L")) + ImageOps.flip(lena("RGB")) - ImageOps.mirror(lena("L")) - ImageOps.mirror(lena("RGB")) + ImageOps.grayscale(lena("L")) + ImageOps.grayscale(lena("RGB")) - ImageOps.posterize(lena("L"), 4) - ImageOps.posterize(lena("RGB"), 4) + ImageOps.invert(lena("L")) + ImageOps.invert(lena("RGB")) - ImageOps.solarize(lena("L")) - ImageOps.solarize(lena("RGB")) + ImageOps.mirror(lena("L")) + ImageOps.mirror(lena("RGB")) - success() + ImageOps.posterize(lena("L"), 4) + ImageOps.posterize(lena("RGB"), 4) -def test_1pxfit(): - # Division by zero in equalize if image is 1 pixel high - newimg = ImageOps.fit(lena("RGB").resize((1,1)), (35,35)) - assert_equal(newimg.size,(35,35)) - - newimg = ImageOps.fit(lena("RGB").resize((1,100)), (35,35)) - assert_equal(newimg.size,(35,35)) + ImageOps.solarize(lena("L")) + ImageOps.solarize(lena("RGB")) - newimg = ImageOps.fit(lena("RGB").resize((100,1)), (35,35)) - assert_equal(newimg.size,(35,35)) + def test_1pxfit(self): + # Division by zero in equalize if image is 1 pixel high + newimg = ImageOps.fit(lena("RGB").resize((1, 1)), (35, 35)) + self.assertEqual(newimg.size, (35, 35)) -def test_pil163(): - # Division by zero in equalize if < 255 pixels in image (@PIL163) + newimg = ImageOps.fit(lena("RGB").resize((1, 100)), (35, 35)) + self.assertEqual(newimg.size, (35, 35)) - i = lena("RGB").resize((15, 16)) + newimg = ImageOps.fit(lena("RGB").resize((100, 1)), (35, 35)) + self.assertEqual(newimg.size, (35, 35)) - ImageOps.equalize(i.convert("L")) - ImageOps.equalize(i.convert("P")) - ImageOps.equalize(i.convert("RGB")) + def test_pil163(self): + # Division by zero in equalize if < 255 pixels in image (@PIL163) - success() + i = lena("RGB").resize((15, 16)) + + ImageOps.equalize(i.convert("L")) + ImageOps.equalize(i.convert("P")) + ImageOps.equalize(i.convert("RGB")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 83a93b8e0..2f81eebce 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,4 +1,4 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image from PIL import ImageOps @@ -6,50 +6,59 @@ from PIL import ImageFilter im = Image.open("Images/lena.ppm") -def test_ops_api(): - i = ImageOps.gaussian_blur(im, 2.0) - assert_equal(i.mode, "RGB") - assert_equal(i.size, (128, 128)) - # i.save("blur.bmp") +class TestImageOpsUsm(PillowTestCase): - i = ImageOps.usm(im, 2.0, 125, 8) - assert_equal(i.mode, "RGB") - assert_equal(i.size, (128, 128)) - # i.save("usm.bmp") + def test_ops_api(self): -def test_filter_api(): + i = ImageOps.gaussian_blur(im, 2.0) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + # i.save("blur.bmp") - filter = ImageFilter.GaussianBlur(2.0) - i = im.filter(filter) - assert_equal(i.mode, "RGB") - assert_equal(i.size, (128, 128)) + i = ImageOps.usm(im, 2.0, 125, 8) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) + # i.save("usm.bmp") - filter = ImageFilter.UnsharpMask(2.0, 125, 8) - i = im.filter(filter) - assert_equal(i.mode, "RGB") - assert_equal(i.size, (128, 128)) + def test_filter_api(self): -def test_usm(): + filter = ImageFilter.GaussianBlur(2.0) + i = im.filter(filter) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) - usm = ImageOps.unsharp_mask - assert_exception(ValueError, lambda: usm(im.convert("1"))) - assert_no_exception(lambda: usm(im.convert("L"))) - assert_exception(ValueError, lambda: usm(im.convert("I"))) - assert_exception(ValueError, lambda: usm(im.convert("F"))) - assert_no_exception(lambda: usm(im.convert("RGB"))) - assert_no_exception(lambda: usm(im.convert("RGBA"))) - assert_no_exception(lambda: usm(im.convert("CMYK"))) - assert_exception(ValueError, lambda: usm(im.convert("YCbCr"))) + filter = ImageFilter.UnsharpMask(2.0, 125, 8) + i = im.filter(filter) + self.assertEqual(i.mode, "RGB") + self.assertEqual(i.size, (128, 128)) -def test_blur(): + def test_usm(self): - blur = ImageOps.gaussian_blur - assert_exception(ValueError, lambda: blur(im.convert("1"))) - assert_no_exception(lambda: blur(im.convert("L"))) - assert_exception(ValueError, lambda: blur(im.convert("I"))) - assert_exception(ValueError, lambda: blur(im.convert("F"))) - assert_no_exception(lambda: blur(im.convert("RGB"))) - assert_no_exception(lambda: blur(im.convert("RGBA"))) - assert_no_exception(lambda: blur(im.convert("CMYK"))) - assert_exception(ValueError, lambda: blur(im.convert("YCbCr"))) + usm = ImageOps.unsharp_mask + self.assertRaises(ValueError, lambda: usm(im.convert("1"))) + usm(im.convert("L")) + self.assertRaises(ValueError, lambda: usm(im.convert("I"))) + self.assertRaises(ValueError, lambda: usm(im.convert("F"))) + usm(im.convert("RGB")) + usm(im.convert("RGBA")) + usm(im.convert("CMYK")) + self.assertRaises(ValueError, lambda: usm(im.convert("YCbCr"))) + + def test_blur(self): + + blur = ImageOps.gaussian_blur + self.assertRaises(ValueError, lambda: blur(im.convert("1"))) + blur(im.convert("L")) + self.assertRaises(ValueError, lambda: blur(im.convert("I"))) + self.assertRaises(ValueError, lambda: blur(im.convert("F"))) + blur(im.convert("RGB")) + blur(im.convert("RGBA")) + blur(im.convert("CMYK")) + self.assertRaises(ValueError, lambda: blur(im.convert("YCbCr"))) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index a22addda9..4d7e067f4 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,44 +1,50 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image from PIL import ImagePalette ImagePalette = ImagePalette.ImagePalette -def test_sanity(): - assert_no_exception(lambda: ImagePalette("RGB", list(range(256))*3)) - assert_exception(ValueError, lambda: ImagePalette("RGB", list(range(256))*2)) +class TestImagePalette(PillowTestCase): -def test_getcolor(): + def test_sanity(self): - palette = ImagePalette() + ImagePalette("RGB", list(range(256))*3) + self.assertRaises( + ValueError, lambda: ImagePalette("RGB", list(range(256))*2)) - map = {} - for i in range(256): - map[palette.getcolor((i, i, i))] = i + def test_getcolor(self): - assert_equal(len(map), 256) - assert_exception(ValueError, lambda: palette.getcolor((1, 2, 3))) + palette = ImagePalette() -def test_file(): + map = {} + for i in range(256): + map[palette.getcolor((i, i, i))] = i - palette = ImagePalette() + self.assertEqual(len(map), 256) + self.assertRaises(ValueError, lambda: palette.getcolor((1, 2, 3))) - file = tempfile("temp.lut") + def test_file(self): - palette.save(file) + palette = ImagePalette() - from PIL.ImagePalette import load, raw + file = self.tempfile("temp.lut") - p = load(file) + palette.save(file) - # load returns raw palette information - assert_equal(len(p[0]), 768) - assert_equal(p[1], "RGB") + from PIL.ImagePalette import load, raw - p = raw(p[1], p[0]) - assert_true(isinstance(p, ImagePalette)) + p = load(file) + + # load returns raw palette information + self.assertEqual(len(p[0]), 768) + self.assertEqual(p[1], "RGB") + + p = raw(p[1], p[0]) + self.assertIsInstance(p, ImagePalette) +if __name__ == '__main__': + unittest.main() +# End of file diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 9e9e63c3a..c293e4225 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,54 +1,68 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image from PIL import ImagePath import array -def test_path(): - p = ImagePath.Path(list(range(10))) +class TestImagePath(PillowTestCase): - # sequence interface - assert_equal(len(p), 5) - assert_equal(p[0], (0.0, 1.0)) - assert_equal(p[-1], (8.0, 9.0)) - assert_equal(list(p[:1]), [(0.0, 1.0)]) - assert_equal(list(p), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) + def test_path(self): - # method sanity check - assert_equal(p.tolist(), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) - assert_equal(p.tolist(1), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) + p = ImagePath.Path(list(range(10))) - assert_equal(p.getbbox(), (0.0, 1.0, 8.0, 9.0)) + # sequence interface + self.assertEqual(len(p), 5) + self.assertEqual(p[0], (0.0, 1.0)) + self.assertEqual(p[-1], (8.0, 9.0)) + self.assertEqual(list(p[:1]), [(0.0, 1.0)]) + self.assertEqual( + list(p), + [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) - assert_equal(p.compact(5), 2) - assert_equal(list(p), [(0.0, 1.0), (4.0, 5.0), (8.0, 9.0)]) + # method sanity check + self.assertEqual( + p.tolist(), + [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]) + self.assertEqual( + p.tolist(1), + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]) - p.transform((1,0,1,0,1,1)) - assert_equal(list(p), [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]) + self.assertEqual(p.getbbox(), (0.0, 1.0, 8.0, 9.0)) - # alternative constructors - p = ImagePath.Path([0, 1]) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path([0.0, 1.0]) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path([0, 1]) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path([(0, 1)]) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(p) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(p.tolist(0)) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(p.tolist(1)) - assert_equal(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(array.array("f", [0, 1])) - assert_equal(list(p), [(0.0, 1.0)]) + self.assertEqual(p.compact(5), 2) + self.assertEqual(list(p), [(0.0, 1.0), (4.0, 5.0), (8.0, 9.0)]) - arr = array.array("f", [0, 1]) - if hasattr(arr, 'tobytes'): - p = ImagePath.Path(arr.tobytes()) - else: - p = ImagePath.Path(arr.tostring()) - assert_equal(list(p), [(0.0, 1.0)]) + p.transform((1, 0, 1, 0, 1, 1)) + self.assertEqual(list(p), [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]) + + # alternative constructors + p = ImagePath.Path([0, 1]) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path([0.0, 1.0]) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path([0, 1]) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path([(0, 1)]) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path(p) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path(p.tolist(0)) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path(p.tolist(1)) + self.assertEqual(list(p), [(0.0, 1.0)]) + p = ImagePath.Path(array.array("f", [0, 1])) + self.assertEqual(list(p), [(0.0, 1.0)]) + + arr = array.array("f", [0, 1]) + if hasattr(arr, 'tobytes'): + p = ImagePath.Path(arr.tobytes()) + else: + p = ImagePath.Path(arr.tostring()) + self.assertEqual(list(p), [(0.0, 1.0)]) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 73d1f4b1c..549fc7fd6 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,37 +1,53 @@ -from tester import * - -from PIL import Image +from helper import unittest, PillowTestCase, tearDownModule, lena try: + from PIL import ImageQt from PyQt5.QtGui import QImage, qRgb, qRgba except: try: from PyQt4.QtGui import QImage, qRgb, qRgba except: - skip('PyQT4 or 5 not installed') - -from PIL import ImageQt - -def test_rgb(): - # from https://qt-project.org/doc/qt-4.8/qcolor.html - # typedef QRgb - # An ARGB quadruplet on the format #AARRGGBB, equivalent to an unsigned int. - - assert_equal(qRgb(0,0,0), qRgba(0,0,0,255)) - - def checkrgb(r,g,b): - val = ImageQt.rgb(r,g,b) - val = val % 2**24 # drop the alpha - assert_equal(val >> 16, r) - assert_equal(((val >> 8 ) % 2**8), g) - assert_equal(val % 2**8, b) - - checkrgb(0,0,0) - checkrgb(255,0,0) - checkrgb(0,255,0) - checkrgb(0,0,255) + # Will be skipped in setUp + pass -def test_image(): - for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): - assert_no_exception(lambda: ImageQt.ImageQt(lena(mode))) +class TestImageQt(PillowTestCase): + + def setUp(self): + try: + from PyQt5.QtGui import QImage, qRgb, qRgba + except: + try: + from PyQt4.QtGui import QImage, qRgb, qRgba + except: + self.skipTest('PyQt4 or 5 not installed') + + def test_rgb(self): + # from https://qt-project.org/doc/qt-4.8/qcolor.html + # typedef QRgb + # An ARGB quadruplet on the format #AARRGGBB, + # equivalent to an unsigned int. + + self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255)) + + def checkrgb(r, g, b): + val = ImageQt.rgb(r, g, b) + val = val % 2**24 # drop the alpha + self.assertEqual(val >> 16, r) + self.assertEqual(((val >> 8) % 2**8), g) + self.assertEqual(val % 2**8, b) + + checkrgb(0, 0, 0) + checkrgb(255, 0, 0) + checkrgb(0, 255, 0) + checkrgb(0, 0, 255) + + def test_image(self): + for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + ImageQt.ImageQt(lena(mode)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 3329b1a05..9001502ac 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,22 +1,29 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena -from PIL import Image from PIL import ImageSequence -def test_sanity(): - file = tempfile("temp.im") +class TestImageSequence(PillowTestCase): - im = lena("RGB") - im.save(file) + def test_sanity(self): - seq = ImageSequence.Iterator(im) + file = self.tempfile("temp.im") - index = 0 - for frame in seq: - assert_image_equal(im, frame) - assert_equal(im.tell(), index) - index = index + 1 + im = lena("RGB") + im.save(file) - assert_equal(index, 1) + seq = ImageSequence.Iterator(im) + index = 0 + for frame in seq: + self.assert_image_equal(im, frame) + self.assertEqual(im.tell(), index) + index = index + 1 + + self.assertEqual(index, 1) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 99ec005c8..08b3ff183 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,6 +1,18 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image from PIL import ImageShow -success() + +class TestImageShow(PillowTestCase): + + def test_sanity(self): + dir(Image) + dir(ImageShow) + pass + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 02a461e22..7eded56cf 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -1,52 +1,63 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageStat -def test_sanity(): - im = lena() +class TestImageStat(PillowTestCase): - st = ImageStat.Stat(im) - st = ImageStat.Stat(im.histogram()) - st = ImageStat.Stat(im, Image.new("1", im.size, 1)) + def test_sanity(self): - assert_no_exception(lambda: st.extrema) - assert_no_exception(lambda: st.sum) - assert_no_exception(lambda: st.mean) - assert_no_exception(lambda: st.median) - assert_no_exception(lambda: st.rms) - assert_no_exception(lambda: st.sum2) - assert_no_exception(lambda: st.var) - assert_no_exception(lambda: st.stddev) - assert_exception(AttributeError, lambda: st.spam) + im = lena() - assert_exception(TypeError, lambda: ImageStat.Stat(1)) + st = ImageStat.Stat(im) + st = ImageStat.Stat(im.histogram()) + st = ImageStat.Stat(im, Image.new("1", im.size, 1)) -def test_lena(): + # Check these run. Exceptions will cause failures. + st.extrema + st.sum + st.mean + st.median + st.rms + st.sum2 + st.var + st.stddev - im = lena() + self.assertRaises(AttributeError, lambda: st.spam) - st = ImageStat.Stat(im) + self.assertRaises(TypeError, lambda: ImageStat.Stat(1)) - # verify a few values - assert_equal(st.extrema[0], (61, 255)) - assert_equal(st.median[0], 197) - assert_equal(st.sum[0], 2954416) - assert_equal(st.sum[1], 2027250) - assert_equal(st.sum[2], 1727331) + def test_lena(self): -def test_constant(): + im = lena() - im = Image.new("L", (128, 128), 128) + st = ImageStat.Stat(im) - st = ImageStat.Stat(im) + # verify a few values + self.assertEqual(st.extrema[0], (61, 255)) + self.assertEqual(st.median[0], 197) + self.assertEqual(st.sum[0], 2954416) + self.assertEqual(st.sum[1], 2027250) + self.assertEqual(st.sum[2], 1727331) - assert_equal(st.extrema[0], (128, 128)) - assert_equal(st.sum[0], 128**3) - assert_equal(st.sum2[0], 128**4) - assert_equal(st.mean[0], 128) - assert_equal(st.median[0], 128) - assert_equal(st.rms[0], 128) - assert_equal(st.var[0], 0) - assert_equal(st.stddev[0], 0) + def test_constant(self): + + im = Image.new("L", (128, 128), 128) + + st = ImageStat.Stat(im) + + self.assertEqual(st.extrema[0], (128, 128)) + self.assertEqual(st.sum[0], 128**3) + self.assertEqual(st.sum2[0], 128**4) + self.assertEqual(st.mean[0], 128) + self.assertEqual(st.median[0], 128) + self.assertEqual(st.rms[0], 128) + self.assertEqual(st.var[0], 0) + self.assertEqual(st.stddev[0], 0) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index b30971e8f..b868096b2 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,9 +1,17 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule -from PIL import Image -try: - from PIL import ImageTk -except (OSError, ImportError) as v: - skip(v) -success() +class TestImageTk(PillowTestCase): + + def test_import(self): + try: + from PIL import ImageTk + dir(ImageTk) + except (OSError, ImportError) as v: + self.skipTest(v) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagetransform.py b/Tests/test_imagetransform.py index 884e6bb1c..dfffafe54 100644 --- a/Tests/test_imagetransform.py +++ b/Tests/test_imagetransform.py @@ -1,18 +1,27 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image from PIL import ImageTransform -im = Image.new("L", (100, 100)) -seq = tuple(range(10)) +class TestImageTransform(PillowTestCase): -def test_sanity(): - transform = ImageTransform.AffineTransform(seq[:6]) - assert_no_exception(lambda: im.transform((100, 100), transform)) - transform = ImageTransform.ExtentTransform(seq[:4]) - assert_no_exception(lambda: im.transform((100, 100), transform)) - transform = ImageTransform.QuadTransform(seq[:8]) - assert_no_exception(lambda: im.transform((100, 100), transform)) - transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) - assert_no_exception(lambda: im.transform((100, 100), transform)) + def test_sanity(self): + im = Image.new("L", (100, 100)) + + seq = tuple(range(10)) + + transform = ImageTransform.AffineTransform(seq[:6]) + im.transform((100, 100), transform) + transform = ImageTransform.ExtentTransform(seq[:4]) + im.transform((100, 100), transform) + transform = ImageTransform.QuadTransform(seq[:8]) + im.transform((100, 100), transform) + transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) + im.transform((100, 100), transform) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index 268a75b6f..f22babbb3 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,6 +1,18 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image from PIL import ImageWin -success() + +class TestImageWin(PillowTestCase): + + def test_sanity(self): + dir(Image) + dir(ImageWin) + pass + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 93aa694d8..c7ea4c701 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,30 +1,39 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -def test_setmode(): - im = Image.new("L", (1, 1), 255) - im.im.setmode("1") - assert_equal(im.im.getpixel((0, 0)), 255) - im.im.setmode("L") - assert_equal(im.im.getpixel((0, 0)), 255) +class TestSanity(PillowTestCase): - im = Image.new("1", (1, 1), 1) - im.im.setmode("L") - assert_equal(im.im.getpixel((0, 0)), 255) - im.im.setmode("1") - assert_equal(im.im.getpixel((0, 0)), 255) + def test_setmode(self): - im = Image.new("RGB", (1, 1), (1, 2, 3)) - im.im.setmode("RGB") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3)) - im.im.setmode("RGBA") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3, 255)) - im.im.setmode("RGBX") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3, 255)) - im.im.setmode("RGB") - assert_equal(im.im.getpixel((0, 0)), (1, 2, 3)) + im = Image.new("L", (1, 1), 255) + im.im.setmode("1") + self.assertEqual(im.im.getpixel((0, 0)), 255) + im.im.setmode("L") + self.assertEqual(im.im.getpixel((0, 0)), 255) - assert_exception(ValueError, lambda: im.im.setmode("L")) - assert_exception(ValueError, lambda: im.im.setmode("RGBABCDE")) + im = Image.new("1", (1, 1), 1) + im.im.setmode("L") + self.assertEqual(im.im.getpixel((0, 0)), 255) + im.im.setmode("1") + self.assertEqual(im.im.getpixel((0, 0)), 255) + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + im.im.setmode("RGB") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3)) + im.im.setmode("RGBA") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3, 255)) + im.im.setmode("RGBX") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3, 255)) + im.im.setmode("RGB") + self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3)) + + self.assertRaises(ValueError, lambda: im.im.setmode("L")) + self.assertRaises(ValueError, lambda: im.im.setmode("RGBABCDE")) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 7675348b3..c8ed39c40 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,138 +1,147 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, py3 from PIL import Image -def pack(): - pass # not yet -def test_pack(): +class TestLibPack(PillowTestCase): - def pack(mode, rawmode): - if len(mode) == 1: - im = Image.new(mode, (1, 1), 1) - else: - im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) + def pack(self): + pass # not yet - if py3: - return list(im.tobytes("raw", rawmode)) - else: - return [ord(c) for c in im.tobytes("raw", rawmode)] + def test_pack(self): - order = 1 if Image._ENDIAN == '<' else -1 + def pack(mode, rawmode): + if len(mode) == 1: + im = Image.new(mode, (1, 1), 1) + else: + im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) - assert_equal(pack("1", "1"), [128]) - assert_equal(pack("1", "1;I"), [0]) - assert_equal(pack("1", "1;R"), [1]) - assert_equal(pack("1", "1;IR"), [0]) + if py3: + return list(im.tobytes("raw", rawmode)) + else: + return [ord(c) for c in im.tobytes("raw", rawmode)] - assert_equal(pack("L", "L"), [1]) + order = 1 if Image._ENDIAN == '<' else -1 - assert_equal(pack("I", "I"), [1, 0, 0, 0][::order]) + self.assertEqual(pack("1", "1"), [128]) + self.assertEqual(pack("1", "1;I"), [0]) + self.assertEqual(pack("1", "1;R"), [1]) + self.assertEqual(pack("1", "1;IR"), [0]) - assert_equal(pack("F", "F"), [0, 0, 128, 63][::order]) + self.assertEqual(pack("L", "L"), [1]) - assert_equal(pack("LA", "LA"), [1, 2]) + self.assertEqual(pack("I", "I"), [1, 0, 0, 0][::order]) - assert_equal(pack("RGB", "RGB"), [1, 2, 3]) - assert_equal(pack("RGB", "RGB;L"), [1, 2, 3]) - assert_equal(pack("RGB", "BGR"), [3, 2, 1]) - assert_equal(pack("RGB", "RGBX"), [1, 2, 3, 255]) # 255? - assert_equal(pack("RGB", "BGRX"), [3, 2, 1, 0]) - assert_equal(pack("RGB", "XRGB"), [0, 1, 2, 3]) - assert_equal(pack("RGB", "XBGR"), [0, 3, 2, 1]) + self.assertEqual(pack("F", "F"), [0, 0, 128, 63][::order]) - assert_equal(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? + self.assertEqual(pack("LA", "LA"), [1, 2]) - assert_equal(pack("RGBA", "RGBA"), [1, 2, 3, 4]) + self.assertEqual(pack("RGB", "RGB"), [1, 2, 3]) + self.assertEqual(pack("RGB", "RGB;L"), [1, 2, 3]) + self.assertEqual(pack("RGB", "BGR"), [3, 2, 1]) + self.assertEqual(pack("RGB", "RGBX"), [1, 2, 3, 255]) # 255? + self.assertEqual(pack("RGB", "BGRX"), [3, 2, 1, 0]) + self.assertEqual(pack("RGB", "XRGB"), [0, 1, 2, 3]) + self.assertEqual(pack("RGB", "XBGR"), [0, 3, 2, 1]) - assert_equal(pack("CMYK", "CMYK"), [1, 2, 3, 4]) - assert_equal(pack("YCbCr", "YCbCr"), [1, 2, 3]) + self.assertEqual(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? -def test_unpack(): + self.assertEqual(pack("RGBA", "RGBA"), [1, 2, 3, 4]) - def unpack(mode, rawmode, bytes_): - im = None + self.assertEqual(pack("CMYK", "CMYK"), [1, 2, 3, 4]) + self.assertEqual(pack("YCbCr", "YCbCr"), [1, 2, 3]) - if py3: - data = bytes(range(1,bytes_+1)) - else: - data = ''.join(chr(i) for i in range(1,bytes_+1)) + def test_unpack(self): - im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1) + def unpack(mode, rawmode, bytes_): + im = None - return im.getpixel((0, 0)) + if py3: + data = bytes(range(1, bytes_+1)) + else: + data = ''.join(chr(i) for i in range(1, bytes_+1)) - def unpack_1(mode, rawmode, value): - assert mode == "1" - im = None + im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1) - if py3: - im = Image.frombytes(mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1) - else: - im = Image.frombytes(mode, (8, 1), chr(value), "raw", rawmode, 0, 1) + return im.getpixel((0, 0)) - return tuple(im.getdata()) + def unpack_1(mode, rawmode, value): + assert mode == "1" + im = None - X = 255 + if py3: + im = Image.frombytes( + mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1) + else: + im = Image.frombytes( + mode, (8, 1), chr(value), "raw", rawmode, 0, 1) - assert_equal(unpack_1("1", "1", 1), (0,0,0,0,0,0,0,X)) - assert_equal(unpack_1("1", "1;I", 1), (X,X,X,X,X,X,X,0)) - assert_equal(unpack_1("1", "1;R", 1), (X,0,0,0,0,0,0,0)) - assert_equal(unpack_1("1", "1;IR", 1), (0,X,X,X,X,X,X,X)) + return tuple(im.getdata()) - assert_equal(unpack_1("1", "1", 170), (X,0,X,0,X,0,X,0)) - assert_equal(unpack_1("1", "1;I", 170), (0,X,0,X,0,X,0,X)) - assert_equal(unpack_1("1", "1;R", 170), (0,X,0,X,0,X,0,X)) - assert_equal(unpack_1("1", "1;IR", 170), (X,0,X,0,X,0,X,0)) + X = 255 - assert_equal(unpack("L", "L;2", 1), 0) - assert_equal(unpack("L", "L;4", 1), 0) - assert_equal(unpack("L", "L", 1), 1) - assert_equal(unpack("L", "L;I", 1), 254) - assert_equal(unpack("L", "L;R", 1), 128) - assert_equal(unpack("L", "L;16", 2), 2) # little endian - assert_equal(unpack("L", "L;16B", 2), 1) # big endian + self.assertEqual(unpack_1("1", "1", 1), (0, 0, 0, 0, 0, 0, 0, X)) + self.assertEqual(unpack_1("1", "1;I", 1), (X, X, X, X, X, X, X, 0)) + self.assertEqual(unpack_1("1", "1;R", 1), (X, 0, 0, 0, 0, 0, 0, 0)) + self.assertEqual(unpack_1("1", "1;IR", 1), (0, X, X, X, X, X, X, X)) - assert_equal(unpack("LA", "LA", 2), (1, 2)) - assert_equal(unpack("LA", "LA;L", 2), (1, 2)) + self.assertEqual(unpack_1("1", "1", 170), (X, 0, X, 0, X, 0, X, 0)) + self.assertEqual(unpack_1("1", "1;I", 170), (0, X, 0, X, 0, X, 0, X)) + self.assertEqual(unpack_1("1", "1;R", 170), (0, X, 0, X, 0, X, 0, X)) + self.assertEqual(unpack_1("1", "1;IR", 170), (X, 0, X, 0, X, 0, X, 0)) - assert_equal(unpack("RGB", "RGB", 3), (1, 2, 3)) - assert_equal(unpack("RGB", "RGB;L", 3), (1, 2, 3)) - assert_equal(unpack("RGB", "RGB;R", 3), (128, 64, 192)) - assert_equal(unpack("RGB", "RGB;16B", 6), (1, 3, 5)) # ? - assert_equal(unpack("RGB", "BGR", 3), (3, 2, 1)) - assert_equal(unpack("RGB", "RGB;15", 2), (8, 131, 0)) - assert_equal(unpack("RGB", "BGR;15", 2), (0, 131, 8)) - assert_equal(unpack("RGB", "RGB;16", 2), (8, 64, 0)) - assert_equal(unpack("RGB", "BGR;16", 2), (0, 64, 8)) - assert_equal(unpack("RGB", "RGB;4B", 2), (17, 0, 34)) + self.assertEqual(unpack("L", "L;2", 1), 0) + self.assertEqual(unpack("L", "L;4", 1), 0) + self.assertEqual(unpack("L", "L", 1), 1) + self.assertEqual(unpack("L", "L;I", 1), 254) + self.assertEqual(unpack("L", "L;R", 1), 128) + self.assertEqual(unpack("L", "L;16", 2), 2) # little endian + self.assertEqual(unpack("L", "L;16B", 2), 1) # big endian - assert_equal(unpack("RGB", "RGBX", 4), (1, 2, 3)) - assert_equal(unpack("RGB", "BGRX", 4), (3, 2, 1)) - assert_equal(unpack("RGB", "XRGB", 4), (2, 3, 4)) - assert_equal(unpack("RGB", "XBGR", 4), (4, 3, 2)) + self.assertEqual(unpack("LA", "LA", 2), (1, 2)) + self.assertEqual(unpack("LA", "LA;L", 2), (1, 2)) - assert_equal(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) - assert_equal(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) - assert_equal(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) - assert_equal(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) - assert_equal(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) - assert_equal(unpack("RGBA", "BGRA;15", 2), (0, 131, 8, 0)) - assert_equal(unpack("RGBA", "RGBA;4B", 2), (17, 0, 34, 0)) + self.assertEqual(unpack("RGB", "RGB", 3), (1, 2, 3)) + self.assertEqual(unpack("RGB", "RGB;L", 3), (1, 2, 3)) + self.assertEqual(unpack("RGB", "RGB;R", 3), (128, 64, 192)) + self.assertEqual(unpack("RGB", "RGB;16B", 6), (1, 3, 5)) # ? + self.assertEqual(unpack("RGB", "BGR", 3), (3, 2, 1)) + self.assertEqual(unpack("RGB", "RGB;15", 2), (8, 131, 0)) + self.assertEqual(unpack("RGB", "BGR;15", 2), (0, 131, 8)) + self.assertEqual(unpack("RGB", "RGB;16", 2), (8, 64, 0)) + self.assertEqual(unpack("RGB", "BGR;16", 2), (0, 64, 8)) + self.assertEqual(unpack("RGB", "RGB;4B", 2), (17, 0, 34)) - assert_equal(unpack("RGBX", "RGBX", 4), (1, 2, 3, 4)) # 4->255? - assert_equal(unpack("RGBX", "BGRX", 4), (3, 2, 1, 255)) - assert_equal(unpack("RGBX", "XRGB", 4), (2, 3, 4, 255)) - assert_equal(unpack("RGBX", "XBGR", 4), (4, 3, 2, 255)) - assert_equal(unpack("RGBX", "RGB;15", 2), (8, 131, 0, 255)) - assert_equal(unpack("RGBX", "BGR;15", 2), (0, 131, 8, 255)) - assert_equal(unpack("RGBX", "RGB;4B", 2), (17, 0, 34, 255)) + self.assertEqual(unpack("RGB", "RGBX", 4), (1, 2, 3)) + self.assertEqual(unpack("RGB", "BGRX", 4), (3, 2, 1)) + self.assertEqual(unpack("RGB", "XRGB", 4), (2, 3, 4)) + self.assertEqual(unpack("RGB", "XBGR", 4), (4, 3, 2)) - assert_equal(unpack("CMYK", "CMYK", 4), (1, 2, 3, 4)) - assert_equal(unpack("CMYK", "CMYK;I", 4), (254, 253, 252, 251)) + self.assertEqual(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) + self.assertEqual(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) + self.assertEqual(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) + self.assertEqual(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) + self.assertEqual(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) + self.assertEqual(unpack("RGBA", "BGRA;15", 2), (0, 131, 8, 0)) + self.assertEqual(unpack("RGBA", "RGBA;4B", 2), (17, 0, 34, 0)) - assert_exception(ValueError, lambda: unpack("L", "L", 0)) - assert_exception(ValueError, lambda: unpack("RGB", "RGB", 2)) - assert_exception(ValueError, lambda: unpack("CMYK", "CMYK", 2)) + self.assertEqual(unpack("RGBX", "RGBX", 4), (1, 2, 3, 4)) # 4->255? + self.assertEqual(unpack("RGBX", "BGRX", 4), (3, 2, 1, 255)) + self.assertEqual(unpack("RGBX", "XRGB", 4), (2, 3, 4, 255)) + self.assertEqual(unpack("RGBX", "XBGR", 4), (4, 3, 2, 255)) + self.assertEqual(unpack("RGBX", "RGB;15", 2), (8, 131, 0, 255)) + self.assertEqual(unpack("RGBX", "BGR;15", 2), (0, 131, 8, 255)) + self.assertEqual(unpack("RGBX", "RGB;4B", 2), (17, 0, 34, 255)) -run() + self.assertEqual(unpack("CMYK", "CMYK", 4), (1, 2, 3, 4)) + self.assertEqual(unpack("CMYK", "CMYK;I", 4), (254, 253, 252, 251)) + + self.assertRaises(ValueError, lambda: unpack("L", "L", 0)) + self.assertRaises(ValueError, lambda: unpack("RGB", "RGB", 2)) + self.assertRaises(ValueError, lambda: unpack("CMYK", "CMYK", 2)) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 6b2b95201..7ef63634b 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,31 +1,39 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena + from PIL import Image import locale # ref https://github.com/python-pillow/Pillow/issues/272 -## on windows, in polish locale: +# on windows, in polish locale: -## import locale -## print locale.setlocale(locale.LC_ALL, 'polish') -## import string -## print len(string.whitespace) -## print ord(string.whitespace[6]) +# import locale +# print locale.setlocale(locale.LC_ALL, 'polish') +# import string +# print len(string.whitespace) +# print ord(string.whitespace[6]) -## Polish_Poland.1250 -## 7 -## 160 +# Polish_Poland.1250 +# 7 +# 160 # one of string.whitespace is not freely convertable into ascii. path = "Images/lena.jpg" -def test_sanity(): - assert_no_exception(lambda: Image.open(path)) - try: - locale.setlocale(locale.LC_ALL, "polish") - except: - skip('polish locale not available') - import string - assert_no_exception(lambda: Image.open(path)) +class TestLocale(PillowTestCase): + + def test_sanity(self): + Image.open(path) + try: + locale.setlocale(locale.LC_ALL, "polish") + except: + unittest.skip('Polish locale not available') + Image.open(path) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 782a26623..d8e205b66 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,107 +1,109 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -def verify(im1): - im2 = lena("I") - assert_equal(im1.size, im2.size) - pix1 = im1.load() - pix2 = im2.load() - for y in range(im1.size[1]): - for x in range(im1.size[0]): - xy = x, y - if pix1[xy] != pix2[xy]: - failure( - "got %r from mode %s at %s, expected %r" % - (pix1[xy], im1.mode, xy, pix2[xy]) - ) - return - success() +class TestModeI16(PillowTestCase): + + def verify(self, im1): + im2 = lena("I") + self.assertEqual(im1.size, im2.size) + pix1 = im1.load() + pix2 = im2.load() + for y in range(im1.size[1]): + for x in range(im1.size[0]): + xy = x, y + self.assertEqual( + pix1[xy], pix2[xy], + ("got %r from mode %s at %s, expected %r" % + (pix1[xy], im1.mode, xy, pix2[xy]))) + + def test_basic(self): + # PIL 1.1 has limited support for 16-bit image data. Check that + # create/copy/transform and save works as expected. + + def basic(mode): + + imIn = lena("I").convert(mode) + self.verify(imIn) + + w, h = imIn.size + + imOut = imIn.copy() + self.verify(imOut) # copy + + imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) + self.verify(imOut) # transform + + filename = self.tempfile("temp.im") + imIn.save(filename) + + imOut = Image.open(filename) + + self.verify(imIn) + self.verify(imOut) + + imOut = imIn.crop((0, 0, w, h)) + self.verify(imOut) + + imOut = Image.new(mode, (w, h), None) + imOut.paste(imIn.crop((0, 0, w//2, h)), (0, 0)) + imOut.paste(imIn.crop((w//2, 0, w, h)), (w//2, 0)) + + self.verify(imIn) + self.verify(imOut) + + imIn = Image.new(mode, (1, 1), 1) + self.assertEqual(imIn.getpixel((0, 0)), 1) + + imIn.putpixel((0, 0), 2) + self.assertEqual(imIn.getpixel((0, 0)), 2) + + if mode == "L": + max = 255 + else: + max = 32767 + + imIn = Image.new(mode, (1, 1), 256) + self.assertEqual(imIn.getpixel((0, 0)), min(256, max)) + + imIn.putpixel((0, 0), 512) + self.assertEqual(imIn.getpixel((0, 0)), min(512, max)) + + basic("L") + + basic("I;16") + basic("I;16B") + basic("I;16L") + + basic("I") + + def test_tobytes(self): + + def tobytes(mode): + return Image.new(mode, (1, 1), 1).tobytes() + + order = 1 if Image._ENDIAN == '<' else -1 + + self.assertEqual(tobytes("L"), b"\x01") + self.assertEqual(tobytes("I;16"), b"\x01\x00") + self.assertEqual(tobytes("I;16B"), b"\x00\x01") + self.assertEqual(tobytes("I"), b"\x01\x00\x00\x00"[::order]) + + def test_convert(self): + + im = lena("I") + + self.verify(im.convert("I;16")) + self.verify(im.convert("I;16").convert("L")) + self.verify(im.convert("I;16").convert("I")) + + self.verify(im.convert("I;16B")) + self.verify(im.convert("I;16B").convert("L")) + self.verify(im.convert("I;16B").convert("I")) -def test_basic(): - # PIL 1.1 has limited support for 16-bit image data. Check that - # create/copy/transform and save works as expected. +if __name__ == '__main__': + unittest.main() - def basic(mode): - - imIn = lena("I").convert(mode) - verify(imIn) - - w, h = imIn.size - - imOut = imIn.copy() - verify(imOut) # copy - - imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) - verify(imOut) # transform - - filename = tempfile("temp.im") - imIn.save(filename) - - imOut = Image.open(filename) - - verify(imIn) - verify(imOut) - - imOut = imIn.crop((0, 0, w, h)) - verify(imOut) - - imOut = Image.new(mode, (w, h), None) - imOut.paste(imIn.crop((0, 0, w//2, h)), (0, 0)) - imOut.paste(imIn.crop((w//2, 0, w, h)), (w//2, 0)) - - verify(imIn) - verify(imOut) - - imIn = Image.new(mode, (1, 1), 1) - assert_equal(imIn.getpixel((0, 0)), 1) - - imIn.putpixel((0, 0), 2) - assert_equal(imIn.getpixel((0, 0)), 2) - - if mode == "L": - max = 255 - else: - max = 32767 - - imIn = Image.new(mode, (1, 1), 256) - assert_equal(imIn.getpixel((0, 0)), min(256, max)) - - imIn.putpixel((0, 0), 512) - assert_equal(imIn.getpixel((0, 0)), min(512, max)) - - basic("L") - - basic("I;16") - basic("I;16B") - basic("I;16L") - - basic("I") - - -def test_tobytes(): - - def tobytes(mode): - return Image.new(mode, (1, 1), 1).tobytes() - - order = 1 if Image._ENDIAN == '<' else -1 - - assert_equal(tobytes("L"), b"\x01") - assert_equal(tobytes("I;16"), b"\x01\x00") - assert_equal(tobytes("I;16B"), b"\x00\x01") - assert_equal(tobytes("I"), b"\x01\x00\x00\x00"[::order]) - - -def test_convert(): - - im = lena("I") - - verify(im.convert("I;16")) - verify(im.convert("I;16").convert("L")) - verify(im.convert("I;16").convert("I")) - - verify(im.convert("I;16B")) - verify(im.convert("I;16B").convert("L")) - verify(im.convert("I;16B").convert("I")) +# End of file diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 9b7881c23..c3c0f7e90 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,120 +1,128 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image -import struct try: import site import numpy except ImportError: - skip() + # Skip via setUp() + pass -def test_numpy_to_image(): - def to_image(dtype, bands=1, bool=0): - if bands == 1: - if bool: - data = [0, 1] * 50 +class TestNumpy(PillowTestCase): + + def setUp(self): + try: + import site + import numpy + except ImportError: + self.skipTest("ImportError") + + def test_numpy_to_image(self): + + def to_image(dtype, bands=1, bool=0): + if bands == 1: + if bool: + data = [0, 1] * 50 + else: + data = list(range(100)) + a = numpy.array(data, dtype=dtype) + a.shape = 10, 10 + i = Image.fromarray(a) + if list(i.getdata()) != data: + print("data mismatch for", dtype) else: data = list(range(100)) - a = numpy.array(data, dtype=dtype) - a.shape = 10, 10 - i = Image.fromarray(a) - if list(i.getdata()) != data: - print("data mismatch for", dtype) + a = numpy.array([[x]*bands for x in data], dtype=dtype) + a.shape = 10, 10, bands + i = Image.fromarray(a) + if list(i.split()[0].getdata()) != list(range(100)): + print("data mismatch for", dtype) + # print dtype, list(i.getdata()) + return i + + # self.assert_image(to_image(numpy.bool, bool=1), "1", (10, 10)) + # self.assert_image(to_image(numpy.bool8, bool=1), "1", (10, 10)) + + self.assertRaises(TypeError, lambda: to_image(numpy.uint)) + self.assert_image(to_image(numpy.uint8), "L", (10, 10)) + self.assertRaises(TypeError, lambda: to_image(numpy.uint16)) + self.assertRaises(TypeError, lambda: to_image(numpy.uint32)) + self.assertRaises(TypeError, lambda: to_image(numpy.uint64)) + + self.assert_image(to_image(numpy.int8), "I", (10, 10)) + if Image._ENDIAN == '<': # Little endian + self.assert_image(to_image(numpy.int16), "I;16", (10, 10)) else: - data = list(range(100)) - a = numpy.array([[x]*bands for x in data], dtype=dtype) - a.shape = 10, 10, bands - i = Image.fromarray(a) - if list(i.split()[0].getdata()) != list(range(100)): - print("data mismatch for", dtype) - # print dtype, list(i.getdata()) - return i + self.assert_image(to_image(numpy.int16), "I;16B", (10, 10)) + self.assert_image(to_image(numpy.int32), "I", (10, 10)) + self.assertRaises(TypeError, lambda: to_image(numpy.int64)) - # assert_image(to_image(numpy.bool, bool=1), "1", (10, 10)) - # assert_image(to_image(numpy.bool8, bool=1), "1", (10, 10)) + self.assert_image(to_image(numpy.float), "F", (10, 10)) + self.assert_image(to_image(numpy.float32), "F", (10, 10)) + self.assert_image(to_image(numpy.float64), "F", (10, 10)) - assert_exception(TypeError, lambda: to_image(numpy.uint)) - assert_image(to_image(numpy.uint8), "L", (10, 10)) - assert_exception(TypeError, lambda: to_image(numpy.uint16)) - assert_exception(TypeError, lambda: to_image(numpy.uint32)) - assert_exception(TypeError, lambda: to_image(numpy.uint64)) + self.assert_image(to_image(numpy.uint8, 3), "RGB", (10, 10)) + self.assert_image(to_image(numpy.uint8, 4), "RGBA", (10, 10)) - assert_image(to_image(numpy.int8), "I", (10, 10)) - if Image._ENDIAN == '<': # Little endian - assert_image(to_image(numpy.int16), "I;16", (10, 10)) - else: - assert_image(to_image(numpy.int16), "I;16B", (10, 10)) - assert_image(to_image(numpy.int32), "I", (10, 10)) - assert_exception(TypeError, lambda: to_image(numpy.int64)) + # based on an erring example at http://is.gd/6F0esS (which resolves to) + # http://stackoverflow.com/questions/10854903/what-is-causing-dimension-dependent-attributeerror-in-pil-fromarray-function + def test_3d_array(self): + a = numpy.ones((10, 10, 10), dtype=numpy.uint8) + self.assert_image(Image.fromarray(a[1, :, :]), "L", (10, 10)) + self.assert_image(Image.fromarray(a[:, 1, :]), "L", (10, 10)) + self.assert_image(Image.fromarray(a[:, :, 1]), "L", (10, 10)) - assert_image(to_image(numpy.float), "F", (10, 10)) - assert_image(to_image(numpy.float32), "F", (10, 10)) - assert_image(to_image(numpy.float64), "F", (10, 10)) + def _test_img_equals_nparray(self, img, np): + self.assertEqual(img.size, np.shape[0:2]) + px = img.load() + for x in range(0, img.size[0], int(img.size[0]/10)): + for y in range(0, img.size[1], int(img.size[1]/10)): + self.assert_deep_equal(px[x, y], np[y, x]) - assert_image(to_image(numpy.uint8, 3), "RGB", (10, 10)) - assert_image(to_image(numpy.uint8, 4), "RGBA", (10, 10)) - - -# based on an erring example at http://is.gd/6F0esS (which resolves to) -# http://stackoverflow.com/questions/10854903/what-is-causing-dimension-dependent-attributeerror-in-pil-fromarray-function -def test_3d_array(): - a = numpy.ones((10, 10, 10), dtype=numpy.uint8) - assert_image(Image.fromarray(a[1, :, :]), "L", (10, 10)) - assert_image(Image.fromarray(a[:, 1, :]), "L", (10, 10)) - assert_image(Image.fromarray(a[:, :, 1]), "L", (10, 10)) - - -def _test_img_equals_nparray(img, np): - assert_equal(img.size, np.shape[0:2]) - px = img.load() - for x in range(0, img.size[0], int(img.size[0]/10)): - for y in range(0, img.size[1], int(img.size[1]/10)): - assert_deep_equal(px[x,y], np[y,x]) - - -def test_16bit(): - img = Image.open('Tests/images/16bit.cropped.tif') - np_img = numpy.array(img) - _test_img_equals_nparray(img, np_img) - assert_equal(np_img.dtype, numpy.dtype('u2'), + ("I;16L", 'u2'), - ("I;16L", ' Date: Tue, 10 Jun 2014 12:12:53 +0300 Subject: [PATCH 111/488] Add test base class and helper functions --- Tests/helper.py | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 Tests/helper.py diff --git a/Tests/helper.py b/Tests/helper.py new file mode 100644 index 000000000..c203e67c2 --- /dev/null +++ b/Tests/helper.py @@ -0,0 +1,341 @@ +""" +Helper functions. +""" +from __future__ import print_function +import sys + +if sys.version_info[:2] <= (2, 6): + import unittest2 as unittest +else: + import unittest + + +def tearDownModule(): + import glob + import os + import tempfile + temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + tempfiles = glob.glob(os.path.join(temp_root, "temp_*")) + if tempfiles: + print("===", "remaining temporary files") + for file in tempfiles: + print(file) + print("-"*68) + + +class PillowTestCase(unittest.TestCase): + + currentResult = None # holds last result object passed to run method + _tempfiles = [] + + def run(self, result=None): + self.addCleanup(self.delete_tempfiles) + self.currentResult = result # remember result for use later + unittest.TestCase.run(self, result) # call superclass run method + + def delete_tempfiles(self): + try: + ok = self.currentResult.wasSuccessful() + except AttributeError: # for nosetests + proxy = self.currentResult + ok = (len(proxy.errors) + len(proxy.failures) == 0) + + if ok: + # only clean out tempfiles if test passed + import os + import os.path + import tempfile + for file in self._tempfiles: + try: + os.remove(file) + except OSError: + pass # report? + temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + try: + os.rmdir(temp_root) + except OSError: + pass + + def assert_almost_equal(self, a, b, msg=None, eps=1e-6): + self.assertLess( + abs(a-b), eps, + msg or "got %r, expected %r" % (a, b)) + + def assert_deep_equal(self, a, b, msg=None): + try: + self.assertEqual( + len(a), len(b), + msg or "got length %s, expected %s" % (len(a), len(b))) + self.assertTrue( + all([x == y for x, y in zip(a, b)]), + msg or "got %s, expected %s" % (a, b)) + except: + self.assertEqual(a, b, msg) + + def assert_image(self, im, mode, size, msg=None): + if mode is not None: + self.assertEqual( + im.mode, mode, + msg or "got mode %r, expected %r" % (im.mode, mode)) + + if size is not None: + self.assertEqual( + im.size, size, + msg or "got size %r, expected %r" % (im.size, size)) + + def assert_image_equal(self, a, b, msg=None): + self.assertEqual( + a.mode, b.mode, + msg or "got mode %r, expected %r" % (a.mode, b.mode)) + self.assertEqual( + a.size, b.size, + msg or "got size %r, expected %r" % (a.size, b.size)) + self.assertEqual( + a.tobytes(), b.tobytes(), + msg or "got different content") + + def assert_image_similar(self, a, b, epsilon, msg=None): + epsilon = float(epsilon) + self.assertEqual( + a.mode, b.mode, + msg or "got mode %r, expected %r" % (a.mode, b.mode)) + self.assertEqual( + a.size, b.size, + msg or "got size %r, expected %r" % (a.size, b.size)) + + diff = 0 + try: + ord(b'0') + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(ord(abyte)-ord(bbyte)) + except: + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(abyte-bbyte) + ave_diff = float(diff)/(a.size[0]*a.size[1]) + self.assertGreaterEqual( + epsilon, ave_diff, + msg or "average pixel value difference %.4f > epsilon %.4f" % ( + ave_diff, epsilon)) + + def assert_warning(self, warn_class, func): + import warnings + + result = None + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Hopefully trigger a warning. + result = func() + + # Verify some things. + self.assertGreaterEqual(len(w), 1) + found = False + for v in w: + if issubclass(v.category, warn_class): + found = True + break + self.assertTrue(found) + return result + + def tempfile(self, template, *extra): + import os + import os.path + import sys + import tempfile + files = [] + root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + try: + os.mkdir(root) + except OSError: + pass + for temp in (template,) + extra: + assert temp[:5] in ("temp.", "temp_") + name = os.path.basename(sys.argv[0]) + name = temp[:4] + os.path.splitext(name)[0][4:] + name = name + "_%d" % len(self._tempfiles) + temp[4:] + name = os.path.join(root, name) + files.append(name) + self._tempfiles.extend(files) + return files[0] + + +# # require that deprecation warnings are triggered +# import warnings +# warnings.simplefilter('default') +# # temporarily turn off resource warnings that warn about unclosed +# # files in the test scripts. +# try: +# warnings.filterwarnings("ignore", category=ResourceWarning) +# except NameError: +# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. +# pass + +import sys +py3 = (sys.version_info >= (3, 0)) + +# # some test helpers +# +# _target = None +# _tempfiles = [] +# _logfile = None +# +# +# def success(): +# import sys +# success.count += 1 +# if _logfile: +# print(sys.argv[0], success.count, failure.count, file=_logfile) +# return True +# +# +# def failure(msg=None, frame=None): +# import sys +# import linecache +# failure.count += 1 +# if _target: +# if frame is None: +# frame = sys._getframe() +# while frame.f_globals.get("__name__") != _target.__name__: +# frame = frame.f_back +# location = (frame.f_code.co_filename, frame.f_lineno) +# prefix = "%s:%d: " % location +# line = linecache.getline(*location) +# print(prefix + line.strip() + " failed:") +# if msg: +# print("- " + msg) +# if _logfile: +# print(sys.argv[0], success.count, failure.count, file=_logfile) +# return False +# +# success.count = failure.count = 0 +# + + +# helpers + +def fromstring(data): + from io import BytesIO + from PIL import Image + return Image.open(BytesIO(data)) + + +def tostring(im, format, **options): + from io import BytesIO + out = BytesIO() + im.save(out, format, **options) + return out.getvalue() + + +def lena(mode="RGB", cache={}): + from PIL import Image + im = None + # im = cache.get(mode) + if im is None: + if mode == "RGB": + im = Image.open("Images/lena.ppm") + elif mode == "F": + im = lena("L").convert(mode) + elif mode[:4] == "I;16": + im = lena("I").convert(mode) + else: + im = lena("RGB").convert(mode) + # cache[mode] = im + return im + + +# def assert_image_completely_equal(a, b, msg=None): +# if a != b: +# failure(msg or "images different") +# else: +# success() +# +# +# # test runner +# +# def run(): +# global _target, _tests, run +# import sys +# import traceback +# _target = sys.modules["__main__"] +# run = None # no need to run twice +# tests = [] +# for name, value in list(vars(_target).items()): +# if name[:5] == "test_" and type(value) is type(success): +# tests.append((value.__code__.co_firstlineno, name, value)) +# tests.sort() # sort by line +# for lineno, name, func in tests: +# try: +# _tests = [] +# func() +# for func, args in _tests: +# func(*args) +# except: +# t, v, tb = sys.exc_info() +# tb = tb.tb_next +# if tb: +# failure(frame=tb.tb_frame) +# traceback.print_exception(t, v, tb) +# else: +# print("%s:%d: cannot call test function: %s" % ( +# sys.argv[0], lineno, v)) +# failure.count += 1 +# +# +# def yield_test(function, *args): +# # collect delayed/generated tests +# _tests.append((function, args)) +# +# +# def skip(msg=None): +# import os +# print("skip") +# os._exit(0) # don't run exit handlers +# +# +# def ignore(pattern): +# """Tells the driver to ignore messages matching the pattern, for the +# duration of the current test.""" +# print('ignore: %s' % pattern) +# +# +# def _setup(): +# global _logfile +# +# import sys +# if "--coverage" in sys.argv: +# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) +# with warnings.catch_warnings(): +# warnings.simplefilter("ignore") +# import coverage +# cov = coverage.coverage(auto_data=True, include="PIL/*") +# cov.start() +# +# def report(): +# if run: +# run() +# if success.count and not failure.count: +# print("ok") +# # only clean out tempfiles if test passed +# import os +# import os.path +# import tempfile +# for file in _tempfiles: +# try: +# os.remove(file) +# except OSError: +# pass # report? +# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') +# try: +# os.rmdir(temp_root) +# except OSError: +# pass +# +# import atexit +# atexit.register(report) +# +# if "--log" in sys.argv: +# _logfile = open("test.log", "a") +# +# +# _setup() From 1686016f1c6fc7ef31776474fdd62f5622eb0304 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 12:20:51 +0300 Subject: [PATCH 112/488] Remove old tester.py so it's not picked up by nosetests as a test --- Tests/tester.py | 388 ------------------------------------------------ 1 file changed, 388 deletions(-) delete mode 100644 Tests/tester.py diff --git a/Tests/tester.py b/Tests/tester.py deleted file mode 100644 index 4476aced1..000000000 --- a/Tests/tester.py +++ /dev/null @@ -1,388 +0,0 @@ -from __future__ import print_function - -# require that deprecation warnings are triggered -import warnings -warnings.simplefilter('default') -# temporarily turn off resource warnings that warn about unclosed -# files in the test scripts. -try: - warnings.filterwarnings("ignore", category=ResourceWarning) -except NameError: - # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. - pass - -import sys -py3 = (sys.version_info >= (3, 0)) - -# some test helpers - -_target = None -_tempfiles = [] -_logfile = None - - -def success(): - import sys - success.count += 1 - if _logfile: - print(sys.argv[0], success.count, failure.count, file=_logfile) - return True - - -def failure(msg=None, frame=None): - import sys - import linecache - failure.count += 1 - if _target: - if frame is None: - frame = sys._getframe() - while frame.f_globals.get("__name__") != _target.__name__: - frame = frame.f_back - location = (frame.f_code.co_filename, frame.f_lineno) - prefix = "%s:%d: " % location - line = linecache.getline(*location) - print(prefix + line.strip() + " failed:") - if msg: - print("- " + msg) - if _logfile: - print(sys.argv[0], success.count, failure.count, file=_logfile) - return False - -success.count = failure.count = 0 - - -# predicates - -def assert_true(v, msg=None): - if v: - success() - else: - failure(msg or "got %r, expected true value" % v) - - -def assert_false(v, msg=None): - if v: - failure(msg or "got %r, expected false value" % v) - else: - success() - - -def assert_equal(a, b, msg=None): - if a == b: - success() - else: - failure(msg or "got %r, expected %r" % (a, b)) - - -def assert_almost_equal(a, b, msg=None, eps=1e-6): - if abs(a-b) < eps: - success() - else: - failure(msg or "got %r, expected %r" % (a, b)) - - -def assert_deep_equal(a, b, msg=None): - try: - if len(a) == len(b): - if all([x == y for x, y in zip(a, b)]): - success() - else: - failure(msg or "got %s, expected %s" % (a, b)) - else: - failure(msg or "got length %s, expected %s" % (len(a), len(b))) - except: - assert_equal(a, b, msg) - - -def assert_greater(a, b, msg=None): - if a > b: - success() - else: - failure(msg or "%r unexpectedly not greater than %r" % (a, b)) - - -def assert_greater_equal(a, b, msg=None): - if a >= b: - success() - else: - failure( - msg or "%r unexpectedly not greater than or equal to %r" % (a, b)) - - -def assert_less(a, b, msg=None): - if a < b: - success() - else: - failure(msg or "%r unexpectedly not less than %r" % (a, b)) - - -def assert_less_equal(a, b, msg=None): - if a <= b: - success() - else: - failure( - msg or "%r unexpectedly not less than or equal to %r" % (a, b)) - - -def assert_is_instance(a, b, msg=None): - if isinstance(a, b): - success() - else: - failure(msg or "got %r, expected %r" % (type(a), b)) - - -def assert_in(a, b, msg=None): - if a in b: - success() - else: - failure(msg or "%r unexpectedly not in %r" % (a, b)) - - -def assert_match(v, pattern, msg=None): - import re - if re.match(pattern, v): - success() - else: - failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) - - -def assert_exception(exc_class, func): - import sys - import traceback - try: - func() - except exc_class: - success() - except: - failure("expected %r exception, got %r" % ( - exc_class.__name__, sys.exc_info()[0].__name__)) - traceback.print_exc() - else: - failure("expected %r exception, got no exception" % exc_class.__name__) - - -def assert_no_exception(func): - import sys - import traceback - try: - func() - except: - failure("expected no exception, got %r" % sys.exc_info()[0].__name__) - traceback.print_exc() - else: - success() - - -def assert_warning(warn_class, func): - # note: this assert calls func three times! - import warnings - - def warn_error(message, category=UserWarning, **options): - raise category(message) - - def warn_ignore(message, category=UserWarning, **options): - pass - warn = warnings.warn - result = None - try: - warnings.warn = warn_ignore - assert_no_exception(func) - result = func() - warnings.warn = warn_error - assert_exception(warn_class, func) - finally: - warnings.warn = warn # restore - return result - -# helpers - -from io import BytesIO - - -def fromstring(data): - from PIL import Image - return Image.open(BytesIO(data)) - - -def tostring(im, format, **options): - out = BytesIO() - im.save(out, format, **options) - return out.getvalue() - - -def lena(mode="RGB", cache={}): - from PIL import Image - im = cache.get(mode) - if im is None: - if mode == "RGB": - im = Image.open("Images/lena.ppm") - elif mode == "F": - im = lena("L").convert(mode) - elif mode[:4] == "I;16": - im = lena("I").convert(mode) - else: - im = lena("RGB").convert(mode) - cache[mode] = im - return im - - -def assert_image(im, mode, size, msg=None): - if mode is not None and im.mode != mode: - failure(msg or "got mode %r, expected %r" % (im.mode, mode)) - elif size is not None and im.size != size: - failure(msg or "got size %r, expected %r" % (im.size, size)) - else: - success() - - -def assert_image_equal(a, b, msg=None): - if a.mode != b.mode: - failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) - elif a.size != b.size: - failure(msg or "got size %r, expected %r" % (a.size, b.size)) - elif a.tobytes() != b.tobytes(): - failure(msg or "got different content") - else: - success() - - -def assert_image_completely_equal(a, b, msg=None): - if a != b: - failure(msg or "images different") - else: - success() - - -def assert_image_similar(a, b, epsilon, msg=None): - epsilon = float(epsilon) - if a.mode != b.mode: - return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) - elif a.size != b.size: - return failure(msg or "got size %r, expected %r" % (a.size, b.size)) - diff = 0 - try: - ord(b'0') - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(ord(abyte)-ord(bbyte)) - except: - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(abyte-bbyte) - ave_diff = float(diff)/(a.size[0]*a.size[1]) - if epsilon < ave_diff: - return failure( - msg or "average pixel value difference %.4f > epsilon %.4f" % ( - ave_diff, epsilon)) - else: - return success() - - -def tempfile(template, *extra): - import os - import os.path - import sys - import tempfile - files = [] - root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.mkdir(root) - except OSError: - pass - for temp in (template,) + extra: - assert temp[:5] in ("temp.", "temp_") - name = os.path.basename(sys.argv[0]) - name = temp[:4] + os.path.splitext(name)[0][4:] - name = name + "_%d" % len(_tempfiles) + temp[4:] - name = os.path.join(root, name) - files.append(name) - _tempfiles.extend(files) - return files[0] - - -# test runner - -def run(): - global _target, _tests, run - import sys - import traceback - _target = sys.modules["__main__"] - run = None # no need to run twice - tests = [] - for name, value in list(vars(_target).items()): - if name[:5] == "test_" and type(value) is type(success): - tests.append((value.__code__.co_firstlineno, name, value)) - tests.sort() # sort by line - for lineno, name, func in tests: - try: - _tests = [] - func() - for func, args in _tests: - func(*args) - except: - t, v, tb = sys.exc_info() - tb = tb.tb_next - if tb: - failure(frame=tb.tb_frame) - traceback.print_exception(t, v, tb) - else: - print("%s:%d: cannot call test function: %s" % ( - sys.argv[0], lineno, v)) - failure.count += 1 - - -def yield_test(function, *args): - # collect delayed/generated tests - _tests.append((function, args)) - - -def skip(msg=None): - import os - print("skip") - os._exit(0) # don't run exit handlers - - -def ignore(pattern): - """Tells the driver to ignore messages matching the pattern, for the - duration of the current test.""" - print('ignore: %s' % pattern) - - -def _setup(): - global _logfile - - import sys - if "--coverage" in sys.argv: - # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - import coverage - cov = coverage.coverage(auto_data=True, include="PIL/*") - cov.start() - - def report(): - if run: - run() - if success.count and not failure.count: - print("ok") - # only clean out tempfiles if test passed - import os - import os.path - import tempfile - for file in _tempfiles: - try: - os.remove(file) - except OSError: - pass # report? - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.rmdir(temp_root) - except OSError: - pass - - import atexit - atexit.register(report) - - if "--log" in sys.argv: - _logfile = open("test.log", "a") - - -_setup() From 0878dfd01062d3344b8bbc15d9f1bd485c132dd9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 12:26:50 +0300 Subject: [PATCH 113/488] Update Travis CI to run new tests based on unittest --- .travis.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1d313a088..284378266 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,8 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls" + - "pip install coveralls nose" + - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp - pushd depends && ./install_webp.sh && popd @@ -28,18 +29,18 @@ script: - python setup.py build_ext --inplace # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests test/; fi # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose test/; fi after_success: - coverage report - coveralls - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py - - pep8 --statistics --count Tests/*.py + - pep8 --statistics --count Tests/*.py - pyflakes PIL/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l) From c8b94113212e88e95f12eac13ad19542296209a3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 12:38:20 +0300 Subject: [PATCH 114/488] Fix test path for Travis CI --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 284378266..576e6e974 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,11 +30,11 @@ script: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests test/; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/; fi # Cover the others - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose test/; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/; fi after_success: - coverage report From 7178279b21196a811f2c2b1942248fedd2c72b8d Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 13:02:52 +0300 Subject: [PATCH 115/488] Specify tests more closely to avoid things like Tests/cms_test.py --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 576e6e974..389051358 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,11 +30,11 @@ script: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi # Cover the others - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi after_success: - coverage report From 77948317044f71133a31a0b2c770dbb5dc0940e3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 10 Jun 2014 13:36:38 +0300 Subject: [PATCH 116/488] Bring back tester.py for now. Not using it for unit tests. --- Tests/tester.py | 388 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 Tests/tester.py diff --git a/Tests/tester.py b/Tests/tester.py new file mode 100644 index 000000000..4476aced1 --- /dev/null +++ b/Tests/tester.py @@ -0,0 +1,388 @@ +from __future__ import print_function + +# require that deprecation warnings are triggered +import warnings +warnings.simplefilter('default') +# temporarily turn off resource warnings that warn about unclosed +# files in the test scripts. +try: + warnings.filterwarnings("ignore", category=ResourceWarning) +except NameError: + # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. + pass + +import sys +py3 = (sys.version_info >= (3, 0)) + +# some test helpers + +_target = None +_tempfiles = [] +_logfile = None + + +def success(): + import sys + success.count += 1 + if _logfile: + print(sys.argv[0], success.count, failure.count, file=_logfile) + return True + + +def failure(msg=None, frame=None): + import sys + import linecache + failure.count += 1 + if _target: + if frame is None: + frame = sys._getframe() + while frame.f_globals.get("__name__") != _target.__name__: + frame = frame.f_back + location = (frame.f_code.co_filename, frame.f_lineno) + prefix = "%s:%d: " % location + line = linecache.getline(*location) + print(prefix + line.strip() + " failed:") + if msg: + print("- " + msg) + if _logfile: + print(sys.argv[0], success.count, failure.count, file=_logfile) + return False + +success.count = failure.count = 0 + + +# predicates + +def assert_true(v, msg=None): + if v: + success() + else: + failure(msg or "got %r, expected true value" % v) + + +def assert_false(v, msg=None): + if v: + failure(msg or "got %r, expected false value" % v) + else: + success() + + +def assert_equal(a, b, msg=None): + if a == b: + success() + else: + failure(msg or "got %r, expected %r" % (a, b)) + + +def assert_almost_equal(a, b, msg=None, eps=1e-6): + if abs(a-b) < eps: + success() + else: + failure(msg or "got %r, expected %r" % (a, b)) + + +def assert_deep_equal(a, b, msg=None): + try: + if len(a) == len(b): + if all([x == y for x, y in zip(a, b)]): + success() + else: + failure(msg or "got %s, expected %s" % (a, b)) + else: + failure(msg or "got length %s, expected %s" % (len(a), len(b))) + except: + assert_equal(a, b, msg) + + +def assert_greater(a, b, msg=None): + if a > b: + success() + else: + failure(msg or "%r unexpectedly not greater than %r" % (a, b)) + + +def assert_greater_equal(a, b, msg=None): + if a >= b: + success() + else: + failure( + msg or "%r unexpectedly not greater than or equal to %r" % (a, b)) + + +def assert_less(a, b, msg=None): + if a < b: + success() + else: + failure(msg or "%r unexpectedly not less than %r" % (a, b)) + + +def assert_less_equal(a, b, msg=None): + if a <= b: + success() + else: + failure( + msg or "%r unexpectedly not less than or equal to %r" % (a, b)) + + +def assert_is_instance(a, b, msg=None): + if isinstance(a, b): + success() + else: + failure(msg or "got %r, expected %r" % (type(a), b)) + + +def assert_in(a, b, msg=None): + if a in b: + success() + else: + failure(msg or "%r unexpectedly not in %r" % (a, b)) + + +def assert_match(v, pattern, msg=None): + import re + if re.match(pattern, v): + success() + else: + failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) + + +def assert_exception(exc_class, func): + import sys + import traceback + try: + func() + except exc_class: + success() + except: + failure("expected %r exception, got %r" % ( + exc_class.__name__, sys.exc_info()[0].__name__)) + traceback.print_exc() + else: + failure("expected %r exception, got no exception" % exc_class.__name__) + + +def assert_no_exception(func): + import sys + import traceback + try: + func() + except: + failure("expected no exception, got %r" % sys.exc_info()[0].__name__) + traceback.print_exc() + else: + success() + + +def assert_warning(warn_class, func): + # note: this assert calls func three times! + import warnings + + def warn_error(message, category=UserWarning, **options): + raise category(message) + + def warn_ignore(message, category=UserWarning, **options): + pass + warn = warnings.warn + result = None + try: + warnings.warn = warn_ignore + assert_no_exception(func) + result = func() + warnings.warn = warn_error + assert_exception(warn_class, func) + finally: + warnings.warn = warn # restore + return result + +# helpers + +from io import BytesIO + + +def fromstring(data): + from PIL import Image + return Image.open(BytesIO(data)) + + +def tostring(im, format, **options): + out = BytesIO() + im.save(out, format, **options) + return out.getvalue() + + +def lena(mode="RGB", cache={}): + from PIL import Image + im = cache.get(mode) + if im is None: + if mode == "RGB": + im = Image.open("Images/lena.ppm") + elif mode == "F": + im = lena("L").convert(mode) + elif mode[:4] == "I;16": + im = lena("I").convert(mode) + else: + im = lena("RGB").convert(mode) + cache[mode] = im + return im + + +def assert_image(im, mode, size, msg=None): + if mode is not None and im.mode != mode: + failure(msg or "got mode %r, expected %r" % (im.mode, mode)) + elif size is not None and im.size != size: + failure(msg or "got size %r, expected %r" % (im.size, size)) + else: + success() + + +def assert_image_equal(a, b, msg=None): + if a.mode != b.mode: + failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) + elif a.size != b.size: + failure(msg or "got size %r, expected %r" % (a.size, b.size)) + elif a.tobytes() != b.tobytes(): + failure(msg or "got different content") + else: + success() + + +def assert_image_completely_equal(a, b, msg=None): + if a != b: + failure(msg or "images different") + else: + success() + + +def assert_image_similar(a, b, epsilon, msg=None): + epsilon = float(epsilon) + if a.mode != b.mode: + return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) + elif a.size != b.size: + return failure(msg or "got size %r, expected %r" % (a.size, b.size)) + diff = 0 + try: + ord(b'0') + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(ord(abyte)-ord(bbyte)) + except: + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(abyte-bbyte) + ave_diff = float(diff)/(a.size[0]*a.size[1]) + if epsilon < ave_diff: + return failure( + msg or "average pixel value difference %.4f > epsilon %.4f" % ( + ave_diff, epsilon)) + else: + return success() + + +def tempfile(template, *extra): + import os + import os.path + import sys + import tempfile + files = [] + root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + try: + os.mkdir(root) + except OSError: + pass + for temp in (template,) + extra: + assert temp[:5] in ("temp.", "temp_") + name = os.path.basename(sys.argv[0]) + name = temp[:4] + os.path.splitext(name)[0][4:] + name = name + "_%d" % len(_tempfiles) + temp[4:] + name = os.path.join(root, name) + files.append(name) + _tempfiles.extend(files) + return files[0] + + +# test runner + +def run(): + global _target, _tests, run + import sys + import traceback + _target = sys.modules["__main__"] + run = None # no need to run twice + tests = [] + for name, value in list(vars(_target).items()): + if name[:5] == "test_" and type(value) is type(success): + tests.append((value.__code__.co_firstlineno, name, value)) + tests.sort() # sort by line + for lineno, name, func in tests: + try: + _tests = [] + func() + for func, args in _tests: + func(*args) + except: + t, v, tb = sys.exc_info() + tb = tb.tb_next + if tb: + failure(frame=tb.tb_frame) + traceback.print_exception(t, v, tb) + else: + print("%s:%d: cannot call test function: %s" % ( + sys.argv[0], lineno, v)) + failure.count += 1 + + +def yield_test(function, *args): + # collect delayed/generated tests + _tests.append((function, args)) + + +def skip(msg=None): + import os + print("skip") + os._exit(0) # don't run exit handlers + + +def ignore(pattern): + """Tells the driver to ignore messages matching the pattern, for the + duration of the current test.""" + print('ignore: %s' % pattern) + + +def _setup(): + global _logfile + + import sys + if "--coverage" in sys.argv: + # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import coverage + cov = coverage.coverage(auto_data=True, include="PIL/*") + cov.start() + + def report(): + if run: + run() + if success.count and not failure.count: + print("ok") + # only clean out tempfiles if test passed + import os + import os.path + import tempfile + for file in _tempfiles: + try: + os.remove(file) + except OSError: + pass # report? + temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + try: + os.rmdir(temp_root) + except OSError: + pass + + import atexit + atexit.register(report) + + if "--log" in sys.argv: + _logfile = open("test.log", "a") + + +_setup() From 3ff590d253e30c149ca9e45f3899666e9a862f34 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 10 Jun 2014 07:42:57 -0400 Subject: [PATCH 117/488] Revert "Update" This reverts commit 1d96522932927f18eb2b7f3b35608ab94a9a86ce. --- CHANGES.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 40d8ed5cd..71a42e315 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,9 +4,6 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ -- Use unittest for tests - [hugovk] - - ImageCms fixes [hugovk] From b2a2f16b234b44d59cd861c97b3bb849e3ca1504 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 10 Jun 2014 07:43:23 -0400 Subject: [PATCH 118/488] Revert "Merge pull request #693 from hugovk/unittest0" This reverts commit 001b46c6708deb96442549d9acb55d7d502e1f6c, reversing changes made to 8beb66443becbd588c4148267fd2ba29c602be57. --- .travis.yml | 18 +- PIL/tests.py | 17 ++ Tests/test_000_sanity.py | 24 +++ Tests/test_bmp_reference.py | 86 +++++++++ Tests/test_file_fli.py | 14 ++ Tests/test_file_icns.py | 66 +++++++ Tests/test_file_ico.py | 14 ++ Tests/test_file_psd.py | 14 ++ {test => Tests}/test_file_xbm.py | 22 +-- Tests/test_file_xpm.py | 14 ++ Tests/test_font_bdf.py | 13 ++ Tests/test_format_lab.py | 41 ++++ Tests/test_image_array.py | 33 ++++ Tests/test_image_copy.py | 12 ++ Tests/test_image_crop.py | 52 ++++++ Tests/test_image_filter.py | 82 ++++++++ Tests/test_image_frombytes.py | 10 + Tests/test_image_getbands.py | 15 ++ Tests/test_image_getbbox.py | 36 ++++ Tests/test_image_getcolors.py | 64 +++++++ Tests/test_image_getextrema.py | 17 ++ Tests/test_image_getim.py | 14 ++ Tests/test_image_getpalette.py | 19 ++ Tests/test_image_histogram.py | 19 ++ Tests/test_image_load.py | 27 +++ Tests/test_image_mode.py | 27 +++ Tests/test_image_offset.py | 16 ++ Tests/test_image_paste.py | 5 + Tests/test_image_putalpha.py | 43 +++++ Tests/test_image_putdata.py | 40 ++++ Tests/test_image_putpalette.py | 28 +++ Tests/test_image_quantize.py | 27 +++ Tests/test_image_resize.py | 12 ++ Tests/test_image_rotate.py | 15 ++ Tests/test_image_save.py | 5 + Tests/test_image_seek.py | 5 + Tests/test_image_show.py | 5 + Tests/test_image_tell.py | 5 + Tests/test_image_thumbnail.py | 36 ++++ Tests/test_image_tobitmap.py | 15 ++ Tests/test_image_tobytes.py | 7 + Tests/test_image_transform.py | 116 ++++++++++++ Tests/test_image_verify.py | 5 + Tests/test_imagechops.py | 56 ++++++ Tests/test_imagecms.py | 203 ++++++++++++++++++++ Tests/test_imagecolor.py | 54 ++++++ Tests/test_imagedraw.py | 270 +++++++++++++++++++++++++++ Tests/test_imageenhance.py | 19 ++ Tests/test_imagefileio.py | 24 +++ Tests/test_imagefilter.py | 31 ++++ Tests/test_imagefont.py | 135 ++++++++++++++ Tests/test_imagegrab.py | 13 ++ Tests/test_imagemath.py | 62 +++++++ Tests/test_imagemode.py | 23 +++ Tests/test_imageops.py | 81 ++++++++ Tests/test_imageshow.py | 6 + Tests/test_imagestat.py | 52 ++++++ Tests/test_imagetk.py | 9 + Tests/test_imagetransform.py | 18 ++ Tests/test_imagewin.py | 6 + Tests/test_lib_image.py | 30 +++ Tests/test_lib_pack.py | 138 ++++++++++++++ Tests/test_locale.py | 31 ++++ test/helper.py | 310 ------------------------------- test/test_000_sanity.py | 32 ---- test/test_bmp_reference.py | 94 ---------- test/test_file_fli.py | 23 --- test/test_file_icns.py | 74 -------- test/test_file_ico.py | 23 --- test/test_file_psd.py | 23 --- test/test_file_xpm.py | 23 --- test/test_font_bdf.py | 22 --- test/test_format_lab.py | 48 ----- test/test_image_array.py | 46 ----- test/test_image_copy.py | 20 -- test/test_image_crop.py | 59 ------ test/test_image_filter.py | 91 --------- test/test_image_frombytes.py | 18 -- test/test_image_getbands.py | 26 --- test/test_image_getbbox.py | 45 ----- test/test_image_getcolors.py | 74 -------- test/test_image_getextrema.py | 27 --- test/test_image_getim.py | 19 -- test/test_image_getpalette.py | 26 --- test/test_image_histogram.py | 26 --- test/test_image_load.py | 35 ---- test/test_image_mode.py | 36 ---- test/test_image_offset.py | 25 --- test/test_image_putalpha.py | 52 ------ test/test_image_putdata.py | 48 ----- test/test_image_putpalette.py | 36 ---- test/test_image_quantize.py | 35 ---- test/test_image_resize.py | 19 -- test/test_image_rotate.py | 22 --- test/test_image_thumbnail.py | 43 ----- test/test_image_tobitmap.py | 22 --- test/test_image_tobytes.py | 13 -- test/test_image_transform.py | 125 ------------- test/test_imagechops.py | 74 -------- test/test_imagecms.py | 214 --------------------- test/test_imagecolor.py | 71 ------- test/test_imagedraw.py | 255 ------------------------- test/test_imageenhance.py | 28 --- test/test_imagefileio.py | 33 ---- test/test_imagefilter.py | 37 ---- test/test_imagefont.py | 145 --------------- test/test_imagegrab.py | 25 --- test/test_imagemath.py | 78 -------- test/test_imagemode.py | 32 ---- test/test_imageops.py | 85 --------- test/test_imageshow.py | 18 -- test/test_imagestat.py | 63 ------- test/test_imagetk.py | 17 -- test/test_imagetransform.py | 27 --- test/test_imagewin.py | 18 -- test/test_lib_image.py | 39 ---- test/test_lib_pack.py | 147 --------------- test/test_locale.py | 39 ---- 118 files changed, 2388 insertions(+), 3133 deletions(-) create mode 100644 PIL/tests.py create mode 100644 Tests/test_000_sanity.py create mode 100644 Tests/test_bmp_reference.py create mode 100644 Tests/test_file_fli.py create mode 100644 Tests/test_file_icns.py create mode 100644 Tests/test_file_ico.py create mode 100644 Tests/test_file_psd.py rename {test => Tests}/test_file_xbm.py (72%) create mode 100644 Tests/test_file_xpm.py create mode 100644 Tests/test_font_bdf.py create mode 100644 Tests/test_format_lab.py create mode 100644 Tests/test_image_array.py create mode 100644 Tests/test_image_copy.py create mode 100644 Tests/test_image_crop.py create mode 100644 Tests/test_image_filter.py create mode 100644 Tests/test_image_frombytes.py create mode 100644 Tests/test_image_getbands.py create mode 100644 Tests/test_image_getbbox.py create mode 100644 Tests/test_image_getcolors.py create mode 100644 Tests/test_image_getextrema.py create mode 100644 Tests/test_image_getim.py create mode 100644 Tests/test_image_getpalette.py create mode 100644 Tests/test_image_histogram.py create mode 100644 Tests/test_image_load.py create mode 100644 Tests/test_image_mode.py create mode 100644 Tests/test_image_offset.py create mode 100644 Tests/test_image_paste.py create mode 100644 Tests/test_image_putalpha.py create mode 100644 Tests/test_image_putdata.py create mode 100644 Tests/test_image_putpalette.py create mode 100644 Tests/test_image_quantize.py create mode 100644 Tests/test_image_resize.py create mode 100644 Tests/test_image_rotate.py create mode 100644 Tests/test_image_save.py create mode 100644 Tests/test_image_seek.py create mode 100644 Tests/test_image_show.py create mode 100644 Tests/test_image_tell.py create mode 100644 Tests/test_image_thumbnail.py create mode 100644 Tests/test_image_tobitmap.py create mode 100644 Tests/test_image_tobytes.py create mode 100644 Tests/test_image_transform.py create mode 100644 Tests/test_image_verify.py create mode 100644 Tests/test_imagechops.py create mode 100644 Tests/test_imagecms.py create mode 100644 Tests/test_imagecolor.py create mode 100644 Tests/test_imagedraw.py create mode 100644 Tests/test_imageenhance.py create mode 100644 Tests/test_imagefileio.py create mode 100644 Tests/test_imagefilter.py create mode 100644 Tests/test_imagefont.py create mode 100644 Tests/test_imagegrab.py create mode 100644 Tests/test_imagemath.py create mode 100644 Tests/test_imagemode.py create mode 100644 Tests/test_imageops.py create mode 100644 Tests/test_imageshow.py create mode 100644 Tests/test_imagestat.py create mode 100644 Tests/test_imagetk.py create mode 100644 Tests/test_imagetransform.py create mode 100644 Tests/test_imagewin.py create mode 100644 Tests/test_lib_image.py create mode 100644 Tests/test_lib_pack.py create mode 100644 Tests/test_locale.py delete mode 100644 test/helper.py delete mode 100644 test/test_000_sanity.py delete mode 100644 test/test_bmp_reference.py delete mode 100644 test/test_file_fli.py delete mode 100644 test/test_file_icns.py delete mode 100644 test/test_file_ico.py delete mode 100644 test/test_file_psd.py delete mode 100644 test/test_file_xpm.py delete mode 100644 test/test_font_bdf.py delete mode 100644 test/test_format_lab.py delete mode 100644 test/test_image_array.py delete mode 100644 test/test_image_copy.py delete mode 100644 test/test_image_crop.py delete mode 100644 test/test_image_filter.py delete mode 100644 test/test_image_frombytes.py delete mode 100644 test/test_image_getbands.py delete mode 100644 test/test_image_getbbox.py delete mode 100644 test/test_image_getcolors.py delete mode 100644 test/test_image_getextrema.py delete mode 100644 test/test_image_getim.py delete mode 100644 test/test_image_getpalette.py delete mode 100644 test/test_image_histogram.py delete mode 100644 test/test_image_load.py delete mode 100644 test/test_image_mode.py delete mode 100644 test/test_image_offset.py delete mode 100644 test/test_image_putalpha.py delete mode 100644 test/test_image_putdata.py delete mode 100644 test/test_image_putpalette.py delete mode 100644 test/test_image_quantize.py delete mode 100644 test/test_image_resize.py delete mode 100644 test/test_image_rotate.py delete mode 100644 test/test_image_thumbnail.py delete mode 100644 test/test_image_tobitmap.py delete mode 100644 test/test_image_tobytes.py delete mode 100644 test/test_image_transform.py delete mode 100644 test/test_imagechops.py delete mode 100644 test/test_imagecms.py delete mode 100644 test/test_imagecolor.py delete mode 100644 test/test_imagedraw.py delete mode 100644 test/test_imageenhance.py delete mode 100644 test/test_imagefileio.py delete mode 100644 test/test_imagefilter.py delete mode 100644 test/test_imagefont.py delete mode 100644 test/test_imagegrab.py delete mode 100644 test/test_imagemath.py delete mode 100644 test/test_imagemode.py delete mode 100644 test/test_imageops.py delete mode 100644 test/test_imageshow.py delete mode 100644 test/test_imagestat.py delete mode 100644 test/test_imagetk.py delete mode 100644 test/test_imagetransform.py delete mode 100644 test/test_imagewin.py delete mode 100644 test/test_lib_image.py delete mode 100644 test/test_lib_pack.py delete mode 100644 test/test_locale.py diff --git a/.travis.yml b/.travis.yml index ef5094a68..1d313a088 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,7 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls nose" - - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi + - "pip install coveralls" # webp - pushd depends && ./install_webp.sh && popd @@ -29,23 +28,18 @@ script: - python setup.py build_ext --inplace # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests test/; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python Tests/run.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose test/; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time python Tests/run.py --coverage; fi - + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi after_success: - coverage report - coveralls - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py - - pep8 --statistics --count test/*.py - - pep8 --statistics --count Tests/*.py + - pep8 --statistics --count Tests/*.py - pyflakes PIL/*.py | tee >(wc -l) - - pyflakes test/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l) diff --git a/PIL/tests.py b/PIL/tests.py new file mode 100644 index 000000000..eb4a8342d --- /dev/null +++ b/PIL/tests.py @@ -0,0 +1,17 @@ +import unittest + + +class PillowTests(unittest.TestCase): + """ + Can we start moving the test suite here? + """ + + def test_suite_should_move_here(self): + """ + Great idea! + """ + assert True is True + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py new file mode 100644 index 000000000..a30786458 --- /dev/null +++ b/Tests/test_000_sanity.py @@ -0,0 +1,24 @@ +from __future__ import print_function +from tester import * + +import PIL +import PIL.Image + +# Make sure we have the binary extension +im = PIL.Image.core.new("L", (100, 100)) + +assert PIL.Image.VERSION[:3] == '1.1' + +# Create an image and do stuff with it. +im = PIL.Image.new("1", (100, 100)) +assert (im.mode, im.size) == ('1', (100, 100)) +assert len(im.tobytes()) == 1300 + +# Create images in all remaining major modes. +im = PIL.Image.new("L", (100, 100)) +im = PIL.Image.new("P", (100, 100)) +im = PIL.Image.new("RGB", (100, 100)) +im = PIL.Image.new("I", (100, 100)) +im = PIL.Image.new("F", (100, 100)) + +print("ok") diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py new file mode 100644 index 000000000..99818229f --- /dev/null +++ b/Tests/test_bmp_reference.py @@ -0,0 +1,86 @@ +from tester import * + +from PIL import Image +import os + +base = os.path.join('Tests', 'images', 'bmp') + + +def get_files(d, ext='.bmp'): + return [os.path.join(base,d,f) for f + in os.listdir(os.path.join(base, d)) if ext in f] + +def test_bad(): + """ These shouldn't crash/dos, but they shouldn't return anything either """ + for f in get_files('b'): + try: + im = Image.open(f) + im.load() + except Exception as msg: + pass + # print ("Bad Image %s: %s" %(f,msg)) + +def test_questionable(): + """ These shouldn't crash/dos, but its not well defined that these are in spec """ + for f in get_files('q'): + try: + im = Image.open(f) + im.load() + except Exception as msg: + pass + # print ("Bad Image %s: %s" %(f,msg)) + + +def test_good(): + """ These should all work. There's a set of target files in the + html directory that we can compare against. """ + + # Target files, if they're not just replacing the extension + file_map = { 'pal1wb.bmp': 'pal1.png', + 'pal4rle.bmp': 'pal4.png', + 'pal8-0.bmp': 'pal8.png', + 'pal8rle.bmp': 'pal8.png', + 'pal8topdown.bmp': 'pal8.png', + 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', + 'pal8os2.bmp': 'pal8.png', + 'pal8os2sp.bmp': 'pal8.png', + 'pal8os2v2.bmp': 'pal8.png', + 'pal8os2v2-16.bmp': 'pal8.png', + 'pal8v4.bmp': 'pal8.png', + 'pal8v5.bmp': 'pal8.png', + 'rgb16-565pal.bmp': 'rgb16-565.png', + 'rgb24pal.bmp': 'rgb24.png', + 'rgb32.bmp': 'rgb24.png', + 'rgb32bf.bmp': 'rgb24.png' + } + + def get_compare(f): + (head, name) = os.path.split(f) + if name in file_map: + return os.path.join(base, 'html', file_map[name]) + (name,ext) = os.path.splitext(name) + return os.path.join(base, 'html', "%s.png"%name) + + for f in get_files('g'): + try: + im = Image.open(f) + im.load() + compare = Image.open(get_compare(f)) + compare.load() + if im.mode == 'P': + # assert image similar doesn't really work + # with paletized image, since the palette might + # be differently ordered for an equivalent image. + im = im.convert('RGBA') + compare = im.convert('RGBA') + assert_image_similar(im, compare,5) + + + except Exception as msg: + # there are three here that are unsupported: + unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), + os.path.join(base, 'g', 'pal8rle.bmp'), + os.path.join(base, 'g', 'pal4rle.bmp')) + if f not in unsupported: + assert_true(False, "Unsupported Image %s: %s" %(f,msg)) + diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py new file mode 100644 index 000000000..4e06a732e --- /dev/null +++ b/Tests/test_file_fli.py @@ -0,0 +1,14 @@ +from tester import * + +from PIL import Image + +# sample ppm stream +file = "Images/lena.fli" +data = open(file, "rb").read() + +def test_sanity(): + im = Image.open(file) + im.load() + assert_equal(im.mode, "P") + assert_equal(im.size, (128, 128)) + assert_equal(im.format, "FLI") diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py new file mode 100644 index 000000000..3e31f8879 --- /dev/null +++ b/Tests/test_file_icns.py @@ -0,0 +1,66 @@ +from tester import * + +from PIL import Image + +# sample icon file +file = "Images/pillow.icns" +data = open(file, "rb").read() + +enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') + +def test_sanity(): + # Loading this icon by default should result in the largest size + # (512x512@2x) being loaded + im = Image.open(file) + im.load() + assert_equal(im.mode, "RGBA") + assert_equal(im.size, (1024, 1024)) + assert_equal(im.format, "ICNS") + +def test_sizes(): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + im = Image.open(file) + for w,h,r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open(file) + im2.size = (w, h, r) + im2.load() + assert_equal(im2.mode, 'RGBA') + assert_equal(im2.size, (wr, hr)) + +def test_older_icon(): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + im = Image.open('Tests/images/pillow2.icns') + for w,h,r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow2.icns') + im2.size = (w, h, r) + im2.load() + assert_equal(im2.mode, 'RGBA') + assert_equal(im2.size, (wr, hr)) + +def test_jp2_icon(): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. + + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) + + if not enable_jpeg2k: + return + + im = Image.open('Tests/images/pillow3.icns') + for w,h,r in im.info['sizes']: + wr = w * r + hr = h * r + im2 = Image.open('Tests/images/pillow3.icns') + im2.size = (w, h, r) + im2.load() + assert_equal(im2.mode, 'RGBA') + assert_equal(im2.size, (wr, hr)) + diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py new file mode 100644 index 000000000..e0db34acc --- /dev/null +++ b/Tests/test_file_ico.py @@ -0,0 +1,14 @@ +from tester import * + +from PIL import Image + +# sample ppm stream +file = "Images/lena.ico" +data = open(file, "rb").read() + +def test_sanity(): + im = Image.open(file) + im.load() + assert_equal(im.mode, "RGBA") + assert_equal(im.size, (16, 16)) + assert_equal(im.format, "ICO") diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py new file mode 100644 index 000000000..ef2d40594 --- /dev/null +++ b/Tests/test_file_psd.py @@ -0,0 +1,14 @@ +from tester import * + +from PIL import Image + +# sample ppm stream +file = "Images/lena.psd" +data = open(file, "rb").read() + +def test_sanity(): + im = Image.open(file) + im.load() + assert_equal(im.mode, "RGB") + assert_equal(im.size, (128, 128)) + assert_equal(im.format, "PSD") diff --git a/test/test_file_xbm.py b/Tests/test_file_xbm.py similarity index 72% rename from test/test_file_xbm.py rename to Tests/test_file_xbm.py index 02aec70b1..f27a3a349 100644 --- a/test/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from tester import * from PIL import Image @@ -25,20 +25,10 @@ static char basic_bits[] = { }; """ +def test_pil151(): -class TestFileXbm(PillowTestCase): + im = Image.open(BytesIO(PIL151)) - def test_pil151(self): - from io import BytesIO - - im = Image.open(BytesIO(PIL151)) - - im.load() - self.assertEqual(im.mode, '1') - self.assertEqual(im.size, (32, 32)) - - -if __name__ == '__main__': - unittest.main() - -# End of file + assert_no_exception(lambda: im.load()) + assert_equal(im.mode, '1') + assert_equal(im.size, (32, 32)) diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py new file mode 100644 index 000000000..44135d028 --- /dev/null +++ b/Tests/test_file_xpm.py @@ -0,0 +1,14 @@ +from tester import * + +from PIL import Image + +# sample ppm stream +file = "Images/lena.xpm" +data = open(file, "rb").read() + +def test_sanity(): + im = Image.open(file) + im.load() + assert_equal(im.mode, "P") + assert_equal(im.size, (128, 128)) + assert_equal(im.format, "XPM") diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py new file mode 100644 index 000000000..366bb4468 --- /dev/null +++ b/Tests/test_font_bdf.py @@ -0,0 +1,13 @@ +from tester import * + +from PIL import Image, FontFile, BdfFontFile + +filename = "Images/courB08.bdf" + +def test_sanity(): + + file = open(filename, "rb") + font = BdfFontFile.BdfFontFile(file) + + assert_true(isinstance(font, FontFile.FontFile)) + assert_equal(len([_f for _f in font.glyph if _f]), 190) diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py new file mode 100644 index 000000000..371b06a0b --- /dev/null +++ b/Tests/test_format_lab.py @@ -0,0 +1,41 @@ +from tester import * + +from PIL import Image + +def test_white(): + i = Image.open('Tests/images/lab.tif') + + bits = i.load() + + assert_equal(i.mode, 'LAB') + + assert_equal(i.getbands(), ('L','A', 'B')) + + k = i.getpixel((0,0)) + assert_equal(k, (255,128,128)) + + L = i.getdata(0) + a = i.getdata(1) + b = i.getdata(2) + + assert_equal(list(L), [255]*100) + assert_equal(list(a), [128]*100) + assert_equal(list(b), [128]*100) + + +def test_green(): + # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS + # == RGB: 0, 152, 117 + i = Image.open('Tests/images/lab-green.tif') + + k = i.getpixel((0,0)) + assert_equal(k, (128,28,128)) + + +def test_red(): + # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS + # == RGB: 255, 0, 124 + i = Image.open('Tests/images/lab-red.tif') + + k = i.getpixel((0,0)) + assert_equal(k, (128,228,128)) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py new file mode 100644 index 000000000..351621d3a --- /dev/null +++ b/Tests/test_image_array.py @@ -0,0 +1,33 @@ +from tester import * + +from PIL import Image + +im = lena().resize((128, 100)) + +def test_toarray(): + def test(mode): + ai = im.convert(mode).__array_interface__ + return ai["shape"], ai["typestr"], len(ai["data"]) + # assert_equal(test("1"), ((100, 128), '|b1', 1600)) + assert_equal(test("L"), ((100, 128), '|u1', 12800)) + assert_equal(test("I"), ((100, 128), Image._ENDIAN + 'i4', 51200)) # FIXME: wrong? + assert_equal(test("F"), ((100, 128), Image._ENDIAN + 'f4', 51200)) # FIXME: wrong? + assert_equal(test("RGB"), ((100, 128, 3), '|u1', 38400)) + assert_equal(test("RGBA"), ((100, 128, 4), '|u1', 51200)) + assert_equal(test("RGBX"), ((100, 128, 4), '|u1', 51200)) + +def test_fromarray(): + def test(mode): + i = im.convert(mode) + a = i.__array_interface__ + a["strides"] = 1 # pretend it's non-contigous + i.__array_interface__ = a # patch in new version of attribute + out = Image.fromarray(i) + return out.mode, out.size, list(i.getdata()) == list(out.getdata()) + # assert_equal(test("1"), ("1", (128, 100), True)) + assert_equal(test("L"), ("L", (128, 100), True)) + assert_equal(test("I"), ("I", (128, 100), True)) + assert_equal(test("F"), ("F", (128, 100), True)) + assert_equal(test("RGB"), ("RGB", (128, 100), True)) + assert_equal(test("RGBA"), ("RGBA", (128, 100), True)) + assert_equal(test("RGBX"), ("RGBA", (128, 100), True)) diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py new file mode 100644 index 000000000..40a3dc496 --- /dev/null +++ b/Tests/test_image_copy.py @@ -0,0 +1,12 @@ +from tester import * + +from PIL import Image + +def test_copy(): + def copy(mode): + im = lena(mode) + out = im.copy() + assert_equal(out.mode, mode) + assert_equal(out.size, im.size) + for mode in "1", "P", "L", "RGB", "I", "F": + yield_test(copy, mode) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py new file mode 100644 index 000000000..973606309 --- /dev/null +++ b/Tests/test_image_crop.py @@ -0,0 +1,52 @@ +from tester import * + +from PIL import Image + +def test_crop(): + def crop(mode): + out = lena(mode).crop((50, 50, 100, 100)) + assert_equal(out.mode, mode) + assert_equal(out.size, (50, 50)) + for mode in "1", "P", "L", "RGB", "I", "F": + yield_test(crop, mode) + +def test_wide_crop(): + + def crop(*bbox): + i = im.crop(bbox) + h = i.histogram() + while h and not h[-1]: + del h[-1] + return tuple(h) + + im = Image.new("L", (100, 100), 1) + + assert_equal(crop(0, 0, 100, 100), (0, 10000)) + assert_equal(crop(25, 25, 75, 75), (0, 2500)) + + # sides + assert_equal(crop(-25, 0, 25, 50), (1250, 1250)) + assert_equal(crop(0, -25, 50, 25), (1250, 1250)) + assert_equal(crop(75, 0, 125, 50), (1250, 1250)) + assert_equal(crop(0, 75, 50, 125), (1250, 1250)) + + assert_equal(crop(-25, 25, 125, 75), (2500, 5000)) + assert_equal(crop(25, -25, 75, 125), (2500, 5000)) + + # corners + assert_equal(crop(-25, -25, 25, 25), (1875, 625)) + assert_equal(crop(75, -25, 125, 25), (1875, 625)) + assert_equal(crop(75, 75, 125, 125), (1875, 625)) + assert_equal(crop(-25, 75, 25, 125), (1875, 625)) + +# -------------------------------------------------------------------- + +def test_negative_crop(): + # Check negative crop size (@PIL171) + + im = Image.new("L", (512, 512)) + im = im.crop((400, 400, 200, 200)) + + assert_equal(im.size, (0, 0)) + assert_equal(len(im.getdata()), 0) + assert_exception(IndexError, lambda: im.getdata()[0]) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py new file mode 100644 index 000000000..f61e0c954 --- /dev/null +++ b/Tests/test_image_filter.py @@ -0,0 +1,82 @@ +from tester import * + +from PIL import Image +from PIL import ImageFilter + +def test_sanity(): + + def filter(filter): + im = lena("L") + out = im.filter(filter) + assert_equal(out.mode, im.mode) + assert_equal(out.size, im.size) + + filter(ImageFilter.BLUR) + filter(ImageFilter.CONTOUR) + filter(ImageFilter.DETAIL) + filter(ImageFilter.EDGE_ENHANCE) + filter(ImageFilter.EDGE_ENHANCE_MORE) + filter(ImageFilter.EMBOSS) + filter(ImageFilter.FIND_EDGES) + filter(ImageFilter.SMOOTH) + filter(ImageFilter.SMOOTH_MORE) + filter(ImageFilter.SHARPEN) + filter(ImageFilter.MaxFilter) + filter(ImageFilter.MedianFilter) + filter(ImageFilter.MinFilter) + filter(ImageFilter.ModeFilter) + filter(ImageFilter.Kernel((3, 3), list(range(9)))) + + assert_exception(TypeError, lambda: filter("hello")) + +def test_crash(): + + # crashes on small images + im = Image.new("RGB", (1, 1)) + assert_no_exception(lambda: im.filter(ImageFilter.SMOOTH)) + + im = Image.new("RGB", (2, 2)) + assert_no_exception(lambda: im.filter(ImageFilter.SMOOTH)) + + im = Image.new("RGB", (3, 3)) + assert_no_exception(lambda: im.filter(ImageFilter.SMOOTH)) + +def test_modefilter(): + + def modefilter(mode): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 + mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + return mod, mod2 + + assert_equal(modefilter("1"), (4, 0)) + assert_equal(modefilter("L"), (4, 0)) + assert_equal(modefilter("P"), (4, 0)) + assert_equal(modefilter("RGB"), ((4, 0, 0), (0, 0, 0))) + +def test_rankfilter(): + + def rankfilter(mode): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + min = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) + med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) + max = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) + return min, med, max + + assert_equal(rankfilter("1"), (0, 4, 8)) + assert_equal(rankfilter("L"), (0, 4, 8)) + assert_exception(ValueError, lambda: rankfilter("P")) + assert_equal(rankfilter("RGB"), ((0, 0, 0), (4, 0, 0), (8, 0, 0))) + assert_equal(rankfilter("I"), (0, 4, 8)) + assert_equal(rankfilter("F"), (0.0, 4.0, 8.0)) diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py new file mode 100644 index 000000000..aa157aa6a --- /dev/null +++ b/Tests/test_image_frombytes.py @@ -0,0 +1,10 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + im1 = lena() + im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) + + assert_image_equal(im1, im2) + diff --git a/Tests/test_image_getbands.py b/Tests/test_image_getbands.py new file mode 100644 index 000000000..e7f1ec5a9 --- /dev/null +++ b/Tests/test_image_getbands.py @@ -0,0 +1,15 @@ +from tester import * + +from PIL import Image + +def test_getbands(): + + assert_equal(Image.new("1", (1, 1)).getbands(), ("1",)) + assert_equal(Image.new("L", (1, 1)).getbands(), ("L",)) + assert_equal(Image.new("I", (1, 1)).getbands(), ("I",)) + assert_equal(Image.new("F", (1, 1)).getbands(), ("F",)) + assert_equal(Image.new("P", (1, 1)).getbands(), ("P",)) + assert_equal(Image.new("RGB", (1, 1)).getbands(), ("R", "G", "B")) + assert_equal(Image.new("RGBA", (1, 1)).getbands(), ("R", "G", "B", "A")) + assert_equal(Image.new("CMYK", (1, 1)).getbands(), ("C", "M", "Y", "K")) + assert_equal(Image.new("YCbCr", (1, 1)).getbands(), ("Y", "Cb", "Cr")) diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py new file mode 100644 index 000000000..c0f846169 --- /dev/null +++ b/Tests/test_image_getbbox.py @@ -0,0 +1,36 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + + bbox = lena().getbbox() + assert_true(isinstance(bbox, tuple)) + +def test_bbox(): + + # 8-bit mode + im = Image.new("L", (100, 100), 0) + assert_equal(im.getbbox(), None) + + im.paste(255, (10, 25, 90, 75)) + assert_equal(im.getbbox(), (10, 25, 90, 75)) + + im.paste(255, (25, 10, 75, 90)) + assert_equal(im.getbbox(), (10, 10, 90, 90)) + + im.paste(255, (-10, -10, 110, 110)) + assert_equal(im.getbbox(), (0, 0, 100, 100)) + + # 32-bit mode + im = Image.new("RGB", (100, 100), 0) + assert_equal(im.getbbox(), None) + + im.paste(255, (10, 25, 90, 75)) + assert_equal(im.getbbox(), (10, 25, 90, 75)) + + im.paste(255, (25, 10, 75, 90)) + assert_equal(im.getbbox(), (10, 10, 90, 90)) + + im.paste(255, (-10, -10, 110, 110)) + assert_equal(im.getbbox(), (0, 0, 100, 100)) diff --git a/Tests/test_image_getcolors.py b/Tests/test_image_getcolors.py new file mode 100644 index 000000000..2429f9350 --- /dev/null +++ b/Tests/test_image_getcolors.py @@ -0,0 +1,64 @@ +from tester import * + +from PIL import Image + +def test_getcolors(): + + def getcolors(mode, limit=None): + im = lena(mode) + if limit: + colors = im.getcolors(limit) + else: + colors = im.getcolors() + if colors: + return len(colors) + return None + + assert_equal(getcolors("1"), 2) + assert_equal(getcolors("L"), 193) + assert_equal(getcolors("I"), 193) + assert_equal(getcolors("F"), 193) + assert_equal(getcolors("P"), 54) # fixed palette + assert_equal(getcolors("RGB"), None) + assert_equal(getcolors("RGBA"), None) + assert_equal(getcolors("CMYK"), None) + assert_equal(getcolors("YCbCr"), None) + + assert_equal(getcolors("L", 128), None) + assert_equal(getcolors("L", 1024), 193) + + assert_equal(getcolors("RGB", 8192), None) + assert_equal(getcolors("RGB", 16384), 14836) + assert_equal(getcolors("RGB", 100000), 14836) + + assert_equal(getcolors("RGBA", 16384), 14836) + assert_equal(getcolors("CMYK", 16384), 14836) + assert_equal(getcolors("YCbCr", 16384), 11995) + +# -------------------------------------------------------------------- + +def test_pack(): + # Pack problems for small tables (@PIL209) + + im = lena().quantize(3).convert("RGB") + + expected = [(3236, (227, 183, 147)), (6297, (143, 84, 81)), (6851, (208, 143, 112))] + + A = im.getcolors(maxcolors=2) + assert_equal(A, None) + + A = im.getcolors(maxcolors=3) + A.sort() + assert_equal(A, expected) + + A = im.getcolors(maxcolors=4) + A.sort() + assert_equal(A, expected) + + A = im.getcolors(maxcolors=8) + A.sort() + assert_equal(A, expected) + + A = im.getcolors(maxcolors=16) + A.sort() + assert_equal(A, expected) diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py new file mode 100644 index 000000000..86106cde0 --- /dev/null +++ b/Tests/test_image_getextrema.py @@ -0,0 +1,17 @@ +from tester import * + +from PIL import Image + +def test_extrema(): + + def extrema(mode): + return lena(mode).getextrema() + + assert_equal(extrema("1"), (0, 255)) + assert_equal(extrema("L"), (40, 235)) + assert_equal(extrema("I"), (40, 235)) + assert_equal(extrema("F"), (40.0, 235.0)) + assert_equal(extrema("P"), (11, 218)) # fixed palette + assert_equal(extrema("RGB"), ((61, 255), (26, 234), (44, 223))) + assert_equal(extrema("RGBA"), ((61, 255), (26, 234), (44, 223), (255, 255))) + assert_equal(extrema("CMYK"), ((0, 194), (21, 229), (32, 211), (0, 0))) diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py new file mode 100644 index 000000000..8d2f12fc2 --- /dev/null +++ b/Tests/test_image_getim.py @@ -0,0 +1,14 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + + im = lena() + type_repr = repr(type(im.getim())) + + if py3: + assert_true("PyCapsule" in type_repr) + + assert_true(isinstance(im.im.id, int)) + diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py new file mode 100644 index 000000000..5dc923b9f --- /dev/null +++ b/Tests/test_image_getpalette.py @@ -0,0 +1,19 @@ +from tester import * + +from PIL import Image + +def test_palette(): + def palette(mode): + p = lena(mode).getpalette() + if p: + return p[:10] + return None + assert_equal(palette("1"), None) + assert_equal(palette("L"), None) + assert_equal(palette("I"), None) + assert_equal(palette("F"), None) + assert_equal(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + assert_equal(palette("RGB"), None) + assert_equal(palette("RGBA"), None) + assert_equal(palette("CMYK"), None) + assert_equal(palette("YCbCr"), None) diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py new file mode 100644 index 000000000..c86cb578a --- /dev/null +++ b/Tests/test_image_histogram.py @@ -0,0 +1,19 @@ +from tester import * + +from PIL import Image + +def test_histogram(): + + def histogram(mode): + h = lena(mode).histogram() + return len(h), min(h), max(h) + + assert_equal(histogram("1"), (256, 0, 8872)) + assert_equal(histogram("L"), (256, 0, 199)) + assert_equal(histogram("I"), (256, 0, 199)) + assert_equal(histogram("F"), (256, 0, 199)) + assert_equal(histogram("P"), (256, 0, 2912)) + assert_equal(histogram("RGB"), (768, 0, 285)) + assert_equal(histogram("RGBA"), (1024, 0, 16384)) + assert_equal(histogram("CMYK"), (1024, 0, 16384)) + assert_equal(histogram("YCbCr"), (768, 0, 741)) diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py new file mode 100644 index 000000000..b385b9686 --- /dev/null +++ b/Tests/test_image_load.py @@ -0,0 +1,27 @@ +from tester import * + +from PIL import Image + +import os + +def test_sanity(): + + im = lena() + + pix = im.load() + + assert_equal(pix[0, 0], (223, 162, 133)) + +def test_close(): + im = Image.open("Images/lena.gif") + assert_no_exception(lambda: im.close()) + assert_exception(ValueError, lambda: im.load()) + assert_exception(ValueError, lambda: im.getpixel((0,0))) + +def test_contextmanager(): + fn = None + with Image.open("Images/lena.gif") as im: + fn = im.fp.fileno() + assert_no_exception(lambda: os.fstat(fn)) + + assert_exception(OSError, lambda: os.fstat(fn)) diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py new file mode 100644 index 000000000..cd5bd47f5 --- /dev/null +++ b/Tests/test_image_mode.py @@ -0,0 +1,27 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + + im = lena() + assert_no_exception(lambda: im.mode) + +def test_properties(): + def check(mode, *result): + signature = ( + Image.getmodebase(mode), Image.getmodetype(mode), + Image.getmodebands(mode), Image.getmodebandnames(mode), + ) + assert_equal(signature, result) + check("1", "L", "L", 1, ("1",)) + check("L", "L", "L", 1, ("L",)) + check("P", "RGB", "L", 1, ("P",)) + check("I", "L", "I", 1, ("I",)) + check("F", "L", "F", 1, ("F",)) + check("RGB", "RGB", "L", 3, ("R", "G", "B")) + check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")) + check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) + check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) + check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")) + check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")) diff --git a/Tests/test_image_offset.py b/Tests/test_image_offset.py new file mode 100644 index 000000000..f6356907a --- /dev/null +++ b/Tests/test_image_offset.py @@ -0,0 +1,16 @@ +from tester import * + +from PIL import Image + +def test_offset(): + + im1 = lena() + + im2 = assert_warning(DeprecationWarning, lambda: im1.offset(10)) + assert_equal(im1.getpixel((0, 0)), im2.getpixel((10, 10))) + + im2 = assert_warning(DeprecationWarning, lambda: im1.offset(10, 20)) + assert_equal(im1.getpixel((0, 0)), im2.getpixel((10, 20))) + + im2 = assert_warning(DeprecationWarning, lambda: im1.offset(20, 20)) + assert_equal(im1.getpixel((0, 0)), im2.getpixel((20, 20))) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py new file mode 100644 index 000000000..7d4b6d9b3 --- /dev/null +++ b/Tests/test_image_paste.py @@ -0,0 +1,5 @@ +from tester import * + +from PIL import Image + +success() diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py new file mode 100644 index 000000000..b23f69834 --- /dev/null +++ b/Tests/test_image_putalpha.py @@ -0,0 +1,43 @@ +from tester import * + +from PIL import Image + +def test_interface(): + + im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) + assert_equal(im.getpixel((0, 0)), (1, 2, 3, 0)) + + im = Image.new("RGBA", (1, 1), (1, 2, 3)) + assert_equal(im.getpixel((0, 0)), (1, 2, 3, 255)) + + im.putalpha(Image.new("L", im.size, 4)) + assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) + + im.putalpha(5) + assert_equal(im.getpixel((0, 0)), (1, 2, 3, 5)) + +def test_promote(): + + im = Image.new("L", (1, 1), 1) + assert_equal(im.getpixel((0, 0)), 1) + + im.putalpha(2) + assert_equal(im.mode, 'LA') + assert_equal(im.getpixel((0, 0)), (1, 2)) + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + assert_equal(im.getpixel((0, 0)), (1, 2, 3)) + + im.putalpha(4) + assert_equal(im.mode, 'RGBA') + assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) + +def test_readonly(): + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + im.readonly = 1 + + im.putalpha(4) + assert_false(im.readonly) + assert_equal(im.mode, 'RGBA') + assert_equal(im.getpixel((0, 0)), (1, 2, 3, 4)) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py new file mode 100644 index 000000000..e25359fdf --- /dev/null +++ b/Tests/test_image_putdata.py @@ -0,0 +1,40 @@ +from tester import * + +import sys + +from PIL import Image + +def test_sanity(): + + im1 = lena() + + data = list(im1.getdata()) + + im2 = Image.new(im1.mode, im1.size, 0) + im2.putdata(data) + + assert_image_equal(im1, im2) + + # readonly + im2 = Image.new(im1.mode, im2.size, 0) + im2.readonly = 1 + im2.putdata(data) + + assert_false(im2.readonly) + assert_image_equal(im1, im2) + + +def test_long_integers(): + # see bug-200802-systemerror + def put(value): + im = Image.new("RGBA", (1, 1)) + im.putdata([value]) + return im.getpixel((0, 0)) + assert_equal(put(0xFFFFFFFF), (255, 255, 255, 255)) + assert_equal(put(0xFFFFFFFF), (255, 255, 255, 255)) + assert_equal(put(-1), (255, 255, 255, 255)) + assert_equal(put(-1), (255, 255, 255, 255)) + if sys.maxsize > 2**32: + assert_equal(put(sys.maxsize), (255, 255, 255, 255)) + else: + assert_equal(put(sys.maxsize), (255, 255, 255, 127)) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py new file mode 100644 index 000000000..b7ebb8853 --- /dev/null +++ b/Tests/test_image_putpalette.py @@ -0,0 +1,28 @@ +from tester import * + +from PIL import Image +from PIL import ImagePalette + +def test_putpalette(): + def palette(mode): + im = lena(mode).copy() + im.putpalette(list(range(256))*3) + p = im.getpalette() + if p: + return im.mode, p[:10] + return im.mode + assert_exception(ValueError, lambda: palette("1")) + assert_equal(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + assert_equal(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + assert_exception(ValueError, lambda: palette("I")) + assert_exception(ValueError, lambda: palette("F")) + assert_exception(ValueError, lambda: palette("RGB")) + assert_exception(ValueError, lambda: palette("RGBA")) + assert_exception(ValueError, lambda: palette("YCbCr")) + +def test_imagepalette(): + im = lena("P") + assert_no_exception(lambda: im.putpalette(ImagePalette.negative())) + assert_no_exception(lambda: im.putpalette(ImagePalette.random())) + assert_no_exception(lambda: im.putpalette(ImagePalette.sepia())) + assert_no_exception(lambda: im.putpalette(ImagePalette.wedge())) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py new file mode 100644 index 000000000..dbf68a25e --- /dev/null +++ b/Tests/test_image_quantize.py @@ -0,0 +1,27 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + + im = lena() + + im = im.quantize() + assert_image(im, "P", im.size) + + im = lena() + im = im.quantize(palette=lena("P")) + assert_image(im, "P", im.size) + +def test_octree_quantize(): + im = lena() + + im = im.quantize(100, Image.FASTOCTREE) + assert_image(im, "P", im.size) + + assert len(im.getcolors()) == 100 + +def test_rgba_quantize(): + im = lena('RGBA') + assert_no_exception(lambda: im.quantize()) + assert_exception(Exception, lambda: im.quantize(method=0)) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py new file mode 100644 index 000000000..4e228a396 --- /dev/null +++ b/Tests/test_image_resize.py @@ -0,0 +1,12 @@ +from tester import * + +from PIL import Image + +def test_resize(): + def resize(mode, size): + out = lena(mode).resize(size) + assert_equal(out.mode, mode) + assert_equal(out.size, size) + for mode in "1", "P", "L", "RGB", "I", "F": + yield_test(resize, mode, (100, 100)) + yield_test(resize, mode, (200, 200)) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py new file mode 100644 index 000000000..5e4782c87 --- /dev/null +++ b/Tests/test_image_rotate.py @@ -0,0 +1,15 @@ +from tester import * + +from PIL import Image + +def test_rotate(): + def rotate(mode): + im = lena(mode) + out = im.rotate(45) + assert_equal(out.mode, mode) + assert_equal(out.size, im.size) # default rotate clips output + out = im.rotate(45, expand=1) + assert_equal(out.mode, mode) + assert_true(out.size != im.size) + for mode in "1", "P", "L", "RGB", "I", "F": + yield_test(rotate, mode) diff --git a/Tests/test_image_save.py b/Tests/test_image_save.py new file mode 100644 index 000000000..7d4b6d9b3 --- /dev/null +++ b/Tests/test_image_save.py @@ -0,0 +1,5 @@ +from tester import * + +from PIL import Image + +success() diff --git a/Tests/test_image_seek.py b/Tests/test_image_seek.py new file mode 100644 index 000000000..7d4b6d9b3 --- /dev/null +++ b/Tests/test_image_seek.py @@ -0,0 +1,5 @@ +from tester import * + +from PIL import Image + +success() diff --git a/Tests/test_image_show.py b/Tests/test_image_show.py new file mode 100644 index 000000000..7d4b6d9b3 --- /dev/null +++ b/Tests/test_image_show.py @@ -0,0 +1,5 @@ +from tester import * + +from PIL import Image + +success() diff --git a/Tests/test_image_tell.py b/Tests/test_image_tell.py new file mode 100644 index 000000000..7d4b6d9b3 --- /dev/null +++ b/Tests/test_image_tell.py @@ -0,0 +1,5 @@ +from tester import * + +from PIL import Image + +success() diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py new file mode 100644 index 000000000..871dd1f54 --- /dev/null +++ b/Tests/test_image_thumbnail.py @@ -0,0 +1,36 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + + im = lena() + im.thumbnail((100, 100)) + + assert_image(im, im.mode, (100, 100)) + +def test_aspect(): + + im = lena() + im.thumbnail((100, 100)) + assert_image(im, im.mode, (100, 100)) + + im = lena().resize((128, 256)) + im.thumbnail((100, 100)) + assert_image(im, im.mode, (50, 100)) + + im = lena().resize((128, 256)) + im.thumbnail((50, 100)) + assert_image(im, im.mode, (50, 100)) + + im = lena().resize((256, 128)) + im.thumbnail((100, 100)) + assert_image(im, im.mode, (100, 50)) + + im = lena().resize((256, 128)) + im.thumbnail((100, 50)) + assert_image(im, im.mode, (100, 50)) + + im = lena().resize((128, 128)) + im.thumbnail((100, 100)) + assert_image(im, im.mode, (100, 100)) diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py new file mode 100644 index 000000000..6fb10dd53 --- /dev/null +++ b/Tests/test_image_tobitmap.py @@ -0,0 +1,15 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + + assert_exception(ValueError, lambda: lena().tobitmap()) + assert_no_exception(lambda: lena().convert("1").tobitmap()) + + im1 = lena().convert("1") + + bitmap = im1.tobitmap() + + assert_true(isinstance(bitmap, bytes)) + assert_image_equal(im1, fromstring(bitmap)) diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py new file mode 100644 index 000000000..d42399993 --- /dev/null +++ b/Tests/test_image_tobytes.py @@ -0,0 +1,7 @@ +from tester import * + +from PIL import Image + +def test_sanity(): + data = lena().tobytes() + assert_true(isinstance(data, bytes)) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py new file mode 100644 index 000000000..dd9b6fe5c --- /dev/null +++ b/Tests/test_image_transform.py @@ -0,0 +1,116 @@ +from tester import * + +from PIL import Image + +def test_extent(): + im = lena('RGB') + (w,h) = im.size + transformed = im.transform(im.size, Image.EXTENT, + (0,0, + w//2,h//2), # ul -> lr + Image.BILINEAR) + + + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) + + assert_image_similar(transformed, scaled, 10) # undone -- precision? + +def test_quad(): + # one simple quad transform, equivalent to scale & crop upper left quad + im = lena('RGB') + (w,h) = im.size + transformed = im.transform(im.size, Image.QUAD, + (0,0,0,h//2, + w//2,h//2,w//2,0), # ul -> ccw around quad + Image.BILINEAR) + + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) + + assert_image_equal(transformed, scaled) + +def test_mesh(): + # this should be a checkerboard of halfsized lenas in ul, lr + im = lena('RGBA') + (w,h) = im.size + transformed = im.transform(im.size, Image.MESH, + [((0,0,w//2,h//2), # box + (0,0,0,h, + w,h,w,0)), # ul -> ccw around quad + ((w//2,h//2,w,h), # box + (0,0,0,h, + w,h,w,0))], # ul -> ccw around quad + Image.BILINEAR) + + #transformed.save('transformed.png') + + scaled = im.resize((w//2, h//2), Image.BILINEAR) + + checker = Image.new('RGBA', im.size) + checker.paste(scaled, (0,0)) + checker.paste(scaled, (w//2,h//2)) + + assert_image_equal(transformed, checker) + + # now, check to see that the extra area is (0,0,0,0) + blank = Image.new('RGBA', (w//2,h//2), (0,0,0,0)) + + assert_image_equal(blank, transformed.crop((w//2,0,w,h//2))) + assert_image_equal(blank, transformed.crop((0,h//2,w//2,h))) + +def _test_alpha_premult(op): + # create image with half white, half black, with the black half transparent. + # do op, + # there should be no darkness in the white section. + im = Image.new('RGBA', (10,10), (0,0,0,0)); + im2 = Image.new('RGBA', (5,10), (255,255,255,255)); + im.paste(im2, (0,0)) + + im = op(im, (40,10)) + im_background = Image.new('RGB', (40,10), (255,255,255)) + im_background.paste(im, (0,0), im) + + hist = im_background.histogram() + assert_equal(40*10, hist[-1]) + + +def test_alpha_premult_resize(): + + def op (im, sz): + return im.resize(sz, Image.LINEAR) + + _test_alpha_premult(op) + +def test_alpha_premult_transform(): + + def op(im, sz): + (w,h) = im.size + return im.transform(sz, Image.EXTENT, + (0,0, + w,h), + Image.BILINEAR) + + _test_alpha_premult(op) + + +def test_blank_fill(): + # attempting to hit + # https://github.com/python-pillow/Pillow/issues/254 reported + # + # issue is that transforms with transparent overflow area + # contained junk from previous images, especially on systems with + # constrained memory. So, attempt to fill up memory with a + # pattern, free it, and then run the mesh test again. Using a 1Mp + # image with 4 bands, for 4 megs of data allocated, x 64. OMM (64 + # bit 12.04 VM with 512 megs available, this fails with Pillow < + # a0eaf06cc5f62a6fb6de556989ac1014ff3348ea + # + # Running by default, but I'd totally understand not doing it in + # the future + + foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) + for a in range(1,65)] + + # Yeah. Watch some JIT optimize this out. + foo = None + + test_mesh() diff --git a/Tests/test_image_verify.py b/Tests/test_image_verify.py new file mode 100644 index 000000000..7d4b6d9b3 --- /dev/null +++ b/Tests/test_image_verify.py @@ -0,0 +1,5 @@ +from tester import * + +from PIL import Image + +success() diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py new file mode 100644 index 000000000..16eaaf55e --- /dev/null +++ b/Tests/test_imagechops.py @@ -0,0 +1,56 @@ +from tester import * + +from PIL import Image +from PIL import ImageChops + +def test_sanity(): + + im = lena("L") + + ImageChops.constant(im, 128) + ImageChops.duplicate(im) + ImageChops.invert(im) + ImageChops.lighter(im, im) + ImageChops.darker(im, im) + ImageChops.difference(im, im) + ImageChops.multiply(im, im) + ImageChops.screen(im, im) + + ImageChops.add(im, im) + ImageChops.add(im, im, 2.0) + ImageChops.add(im, im, 2.0, 128) + ImageChops.subtract(im, im) + ImageChops.subtract(im, im, 2.0) + ImageChops.subtract(im, im, 2.0, 128) + + ImageChops.add_modulo(im, im) + ImageChops.subtract_modulo(im, im) + + ImageChops.blend(im, im, 0.5) + ImageChops.composite(im, im, im) + + ImageChops.offset(im, 10) + ImageChops.offset(im, 10, 20) + +def test_logical(): + + def table(op, a, b): + out = [] + for x in (a, b): + imx = Image.new("1", (1, 1), x) + for y in (a, b): + imy = Image.new("1", (1, 1), y) + out.append(op(imx, imy).getpixel((0, 0))) + return tuple(out) + + assert_equal(table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) + assert_equal(table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) + assert_equal(table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) + + assert_equal(table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) + assert_equal(table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) + assert_equal(table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) + + assert_equal(table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) + assert_equal(table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) + assert_equal(table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py new file mode 100644 index 000000000..f52101eb1 --- /dev/null +++ b/Tests/test_imagecms.py @@ -0,0 +1,203 @@ +from tester import * + +from PIL import Image +try: + from PIL import ImageCms + ImageCms.core.profile_open +except ImportError: + skip() + +SRGB = "Tests/icc/sRGB.icm" + + +def test_sanity(): + + # basic smoke test. + # this mostly follows the cms_test outline. + + v = ImageCms.versions() # should return four strings + assert_equal(v[0], '1.0.0 pil') + assert_equal(list(map(type, v)), [str, str, str, str]) + + # internal version number + assert_match(ImageCms.core.littlecms_version, "\d+\.\d+$") + + i = ImageCms.profileToProfile(lena(), SRGB, SRGB) + assert_image(i, "RGB", (128, 128)) + + i = lena() + ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) + assert_image(i, "RGB", (128, 128)) + + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + i = ImageCms.applyTransform(lena(), t) + assert_image(i, "RGB", (128, 128)) + + i = lena() + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + ImageCms.applyTransform(lena(), t, inPlace=True) + assert_image(i, "RGB", (128, 128)) + + p = ImageCms.createProfile("sRGB") + o = ImageCms.getOpenProfile(SRGB) + t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") + i = ImageCms.applyTransform(lena(), t) + assert_image(i, "RGB", (128, 128)) + + t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") + assert_equal(t.inputMode, "RGB") + assert_equal(t.outputMode, "RGB") + i = ImageCms.applyTransform(lena(), t) + assert_image(i, "RGB", (128, 128)) + + # test PointTransform convenience API + lena().point(t) + + +def test_name(): + # get profile information for file + assert_equal(ImageCms.getProfileName(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + +def test_info(): + assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), + ['sRGB IEC61966-2.1', '', + 'Copyright (c) 1998 Hewlett-Packard Company', '']) + + +def test_copyright(): + assert_equal(ImageCms.getProfileCopyright(SRGB).strip(), + 'Copyright (c) 1998 Hewlett-Packard Company') + + +def test_manufacturer(): + assert_equal(ImageCms.getProfileManufacturer(SRGB).strip(), + 'IEC http://www.iec.ch') + + +def test_model(): + assert_equal(ImageCms.getProfileModel(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + +def test_description(): + assert_equal(ImageCms.getProfileDescription(SRGB).strip(), + 'sRGB IEC61966-2.1') + + +def test_intent(): + assert_equal(ImageCms.getDefaultIntent(SRGB), 0) + assert_equal(ImageCms.isIntentSupported( + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + + +def test_profile_object(): + # same, using profile object + p = ImageCms.createProfile("sRGB") +# assert_equal(ImageCms.getProfileName(p).strip(), +# 'sRGB built-in - (lcms internal)') +# assert_equal(ImageCms.getProfileInfo(p).splitlines(), +# ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) + assert_equal(ImageCms.getDefaultIntent(p), 0) + assert_equal(ImageCms.isIntentSupported( + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + + +def test_extensions(): + # extensions + i = Image.open("Tests/images/rgb.jpg") + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + assert_equal(ImageCms.getProfileName(p).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + +def test_exceptions(): + # the procedural pyCMS API uses PyCMSError for all sorts of errors + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.getProfileName(None)) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.isIntentSupported(SRGB, None, None)) + + +def test_display_profile(): + # try fetching the profile for the current display device + assert_no_exception(lambda: ImageCms.get_display_profile()) + + +def test_lab_color_profile(): + ImageCms.createProfile("LAB", 5000) + ImageCms.createProfile("LAB", 6500) + + +def test_simple_lab(): + i = Image.new('RGB', (10, 10), (128, 128, 128)) + + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + + i_lab = ImageCms.applyTransform(i, t) + + assert_equal(i_lab.mode, 'LAB') + + k = i_lab.getpixel((0, 0)) + assert_equal(k, (137, 128, 128)) # not a linear luminance map. so L != 128 + + L = i_lab.getdata(0) + a = i_lab.getdata(1) + b = i_lab.getdata(2) + + assert_equal(list(L), [137] * 100) + assert_equal(list(a), [128] * 100) + assert_equal(list(b), [128] * 100) + + +def test_lab_color(): + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + # Need to add a type mapping for some PIL type to TYPE_Lab_8 in + # findLCMSType, and have that mapping work back to a PIL mode (likely RGB). + i = ImageCms.applyTransform(lena(), t) + assert_image(i, "LAB", (128, 128)) + + # i.save('temp.lab.tif') # visually verified vs PS. + + target = Image.open('Tests/images/lena.Lab.tif') + + assert_image_similar(i, target, 30) + + +def test_lab_srgb(): + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") + + img = Image.open('Tests/images/lena.Lab.tif') + + img_srgb = ImageCms.applyTransform(img, t) + + # img_srgb.save('temp.srgb.tif') # visually verified vs ps. + + assert_image_similar(lena(), img_srgb, 30) + + +def test_lab_roundtrip(): + # check to see if we're at least internally consistent. + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") + + t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") + + i = ImageCms.applyTransform(lena(), t) + out = ImageCms.applyTransform(i, t2) + + assert_image_similar(lena(), out, 2) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py new file mode 100644 index 000000000..c67c20255 --- /dev/null +++ b/Tests/test_imagecolor.py @@ -0,0 +1,54 @@ +from tester import * + +from PIL import Image +from PIL import ImageColor + +# -------------------------------------------------------------------- +# sanity + +assert_equal((255, 0, 0), ImageColor.getrgb("#f00")) +assert_equal((255, 0, 0), ImageColor.getrgb("#ff0000")) +assert_equal((255, 0, 0), ImageColor.getrgb("rgb(255,0,0)")) +assert_equal((255, 0, 0), ImageColor.getrgb("rgb(255, 0, 0)")) +assert_equal((255, 0, 0), ImageColor.getrgb("rgb(100%,0%,0%)")) +assert_equal((255, 0, 0), ImageColor.getrgb("hsl(0, 100%, 50%)")) +assert_equal((255, 0, 0, 0), ImageColor.getrgb("rgba(255,0,0,0)")) +assert_equal((255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) +assert_equal((255, 0, 0), ImageColor.getrgb("red")) + +# -------------------------------------------------------------------- +# look for rounding errors (based on code by Tim Hatch) + +for color in list(ImageColor.colormap.keys()): + expected = Image.new("RGB", (1, 1), color).convert("L").getpixel((0, 0)) + actual = Image.new("L", (1, 1), color).getpixel((0, 0)) + assert_equal(expected, actual) + +assert_equal((0, 0, 0), ImageColor.getcolor("black", "RGB")) +assert_equal((255, 255, 255), ImageColor.getcolor("white", "RGB")) +assert_equal((0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) +Image.new("RGB", (1, 1), "white") + +assert_equal((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) +assert_equal((255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) +assert_equal((0, 255, 115, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) +Image.new("RGBA", (1, 1), "white") + +assert_equal(0, ImageColor.getcolor("black", "L")) +assert_equal(255, ImageColor.getcolor("white", "L")) +assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) +Image.new("L", (1, 1), "white") + +assert_equal(0, ImageColor.getcolor("black", "1")) +assert_equal(255, ImageColor.getcolor("white", "1")) +# The following test is wrong, but is current behavior +# The correct result should be 255 due to the mode 1 +assert_equal(162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) +# Correct behavior +# assert_equal(255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) +Image.new("1", (1, 1), "white") + +assert_equal((0, 255), ImageColor.getcolor("black", "LA")) +assert_equal((255, 255), ImageColor.getcolor("white", "LA")) +assert_equal((162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) +Image.new("LA", (1, 1), "white") diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py new file mode 100644 index 000000000..7b682020e --- /dev/null +++ b/Tests/test_imagedraw.py @@ -0,0 +1,270 @@ +from tester import * + +from PIL import Image +from PIL import ImageColor +from PIL import ImageDraw + +# Image size +w, h = 100, 100 + +# Bounding box points +x0 = int(w / 4) +x1 = int(x0 * 3) +y0 = int(h / 4) +y1 = int(x0 * 3) + +# Two kinds of bounding box +bbox1 = [(x0, y0), (x1, y1)] +bbox2 = [x0, y0, x1, y1] + +# Two kinds of coordinate sequences +points1 = [(10, 10), (20, 40), (30, 30)] +points2 = [10, 10, 20, 40, 30, 30] + + +def test_sanity(): + + im = lena("RGB").copy() + + draw = ImageDraw.ImageDraw(im) + draw = ImageDraw.Draw(im) + + draw.ellipse(list(range(4))) + draw.line(list(range(10))) + draw.polygon(list(range(100))) + draw.rectangle(list(range(4))) + + success() + + +def test_deprecated(): + + im = lena().copy() + + draw = ImageDraw.Draw(im) + + assert_warning(DeprecationWarning, lambda: draw.setink(0)) + assert_warning(DeprecationWarning, lambda: draw.setfill(0)) + + +def helper_arc(bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + # FIXME Fill param should be named outline. + draw.arc(bbox, 0, 180) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_arc.png")) + + +def test_arc1(): + helper_arc(bbox1) + + +def test_arc2(): + helper_arc(bbox2) + + +def test_bitmap(): + # Arrange + small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.bitmap((10, 10), small) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) + + +def helper_chord(bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.chord(bbox, 0, 180, fill="red", outline="yellow") + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_chord.png")) + + +def test_chord1(): + helper_chord(bbox1) + + +def test_chord2(): + helper_chord(bbox2) + + +def helper_ellipse(bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.ellipse(bbox, fill="green", outline="blue") + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_ellipse.png")) + + +def test_ellipse1(): + helper_ellipse(bbox1) + + +def test_ellipse2(): + helper_ellipse(bbox2) + + +def helper_line(points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.line(points1, fill="yellow", width=2) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + + +def test_line1(): + helper_line(points1) + + +def test_line2(): + helper_line(points2) + + +def helper_pieslice(bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.pieslice(bbox, -90, 45, fill="white", outline="blue") + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) + + +def test_pieslice1(): + helper_pieslice(bbox1) + + +def test_pieslice2(): + helper_pieslice(bbox2) + + +def helper_point(points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.point(points1, fill="yellow") + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) + + +def test_point1(): + helper_point(points1) + + +def test_point2(): + helper_point(points2) + + +def helper_polygon(points): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.polygon(points1, fill="red", outline="blue") + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) + + +def test_polygon1(): + helper_polygon(points1) + + +def test_polygon2(): + helper_polygon(points2) + + +def helper_rectangle(bbox): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(bbox, fill="black", outline="green") + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) + + +def test_rectangle1(): + helper_rectangle(bbox1) + + +def test_rectangle2(): + helper_rectangle(bbox2) + + +def test_floodfill(): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) + + +def test_floodfill_border(): + # floodfill() is experimental + if hasattr(sys, 'pypy_version_info'): + # Causes fatal RPython error on PyPy + skip() + + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill( + im, centre_point, ImageColor.getrgb("red"), + border=ImageColor.getrgb("black")) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + + +# End of file diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py new file mode 100644 index 000000000..04f16bfa5 --- /dev/null +++ b/Tests/test_imageenhance.py @@ -0,0 +1,19 @@ +from tester import * + +from PIL import Image +from PIL import ImageEnhance + +def test_sanity(): + + # FIXME: assert_image + assert_no_exception(lambda: ImageEnhance.Color(lena()).enhance(0.5)) + assert_no_exception(lambda: ImageEnhance.Contrast(lena()).enhance(0.5)) + assert_no_exception(lambda: ImageEnhance.Brightness(lena()).enhance(0.5)) + assert_no_exception(lambda: ImageEnhance.Sharpness(lena()).enhance(0.5)) + +def test_crash(): + + # crashes on small images + im = Image.new("RGB", (1, 1)) + assert_no_exception(lambda: ImageEnhance.Sharpness(im).enhance(0.5)) + diff --git a/Tests/test_imagefileio.py b/Tests/test_imagefileio.py new file mode 100644 index 000000000..c63f07bb0 --- /dev/null +++ b/Tests/test_imagefileio.py @@ -0,0 +1,24 @@ +from tester import * + +from PIL import Image +from PIL import ImageFileIO + +def test_fileio(): + + class DumbFile: + def __init__(self, data): + self.data = data + def read(self, bytes=None): + assert_equal(bytes, None) + return self.data + def close(self): + pass + + im1 = lena() + + io = ImageFileIO.ImageFileIO(DumbFile(tostring(im1, "PPM"))) + + im2 = Image.open(io) + assert_image_equal(im1, im2) + + diff --git a/Tests/test_imagefilter.py b/Tests/test_imagefilter.py new file mode 100644 index 000000000..214f88024 --- /dev/null +++ b/Tests/test_imagefilter.py @@ -0,0 +1,31 @@ +from tester import * + +from PIL import Image +from PIL import ImageFilter + +def test_sanity(): + # see test_image_filter for more tests + + assert_no_exception(lambda: ImageFilter.MaxFilter) + assert_no_exception(lambda: ImageFilter.MedianFilter) + assert_no_exception(lambda: ImageFilter.MinFilter) + assert_no_exception(lambda: ImageFilter.ModeFilter) + assert_no_exception(lambda: ImageFilter.Kernel((3, 3), list(range(9)))) + assert_no_exception(lambda: ImageFilter.GaussianBlur) + assert_no_exception(lambda: ImageFilter.GaussianBlur(5)) + assert_no_exception(lambda: ImageFilter.UnsharpMask) + assert_no_exception(lambda: ImageFilter.UnsharpMask(10)) + + assert_no_exception(lambda: ImageFilter.BLUR) + assert_no_exception(lambda: ImageFilter.CONTOUR) + assert_no_exception(lambda: ImageFilter.DETAIL) + assert_no_exception(lambda: ImageFilter.EDGE_ENHANCE) + assert_no_exception(lambda: ImageFilter.EDGE_ENHANCE_MORE) + assert_no_exception(lambda: ImageFilter.EMBOSS) + assert_no_exception(lambda: ImageFilter.FIND_EDGES) + assert_no_exception(lambda: ImageFilter.SMOOTH) + assert_no_exception(lambda: ImageFilter.SMOOTH_MORE) + assert_no_exception(lambda: ImageFilter.SHARPEN) + + + diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py new file mode 100644 index 000000000..9ac2cdd89 --- /dev/null +++ b/Tests/test_imagefont.py @@ -0,0 +1,135 @@ +from tester import * + +from PIL import Image +from io import BytesIO +import os + +try: + from PIL import ImageFont + ImageFont.core.getfont # check if freetype is available +except ImportError: + skip() + +from PIL import ImageDraw + +font_path = "Tests/fonts/FreeMono.ttf" +font_size=20 + +def test_sanity(): + assert_match(ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") + +def test_font_with_name(): + assert_no_exception(lambda: ImageFont.truetype(font_path, font_size)) + assert_no_exception(lambda: _render(font_path)) + _clean() + +def _font_as_bytes(): + with open(font_path, 'rb') as f: + font_bytes = BytesIO(f.read()) + return font_bytes + +def test_font_with_filelike(): + assert_no_exception(lambda: ImageFont.truetype(_font_as_bytes(), font_size)) + assert_no_exception(lambda: _render(_font_as_bytes())) + # Usage note: making two fonts from the same buffer fails. + #shared_bytes = _font_as_bytes() + #assert_no_exception(lambda: _render(shared_bytes)) + #assert_exception(Exception, lambda: _render(shared_bytes)) + _clean() + +def test_font_with_open_file(): + with open(font_path, 'rb') as f: + assert_no_exception(lambda: _render(f)) + _clean() + +def test_font_old_parameters(): + assert_warning(DeprecationWarning, lambda: ImageFont.truetype(filename=font_path, size=font_size)) + +def _render(font): + txt = "Hello World!" + ttf = ImageFont.truetype(font, font_size) + w, h = ttf.getsize(txt) + img = Image.new("RGB", (256, 64), "white") + d = ImageDraw.Draw(img) + d.text((10, 10), txt, font=ttf, fill='black') + + img.save('font.png') + return img + +def _clean(): + os.unlink('font.png') + +def test_render_equal(): + img_path = _render(font_path) + with open(font_path, 'rb') as f: + font_filelike = BytesIO(f.read()) + img_filelike = _render(font_filelike) + + assert_image_equal(img_path, img_filelike) + _clean() + + +def test_render_multiline(): + im = Image.new(mode='RGB', size=(300,100)) + draw = ImageDraw.Draw(im) + ttf = ImageFont.truetype(font_path, font_size) + line_spacing = draw.textsize('A', font=ttf)[1] + 8 + lines = ['hey you', 'you are awesome', 'this looks awkward'] + y = 0 + for line in lines: + draw.text((0, y), line, font=ttf) + y += line_spacing + + + target = 'Tests/images/multiline_text.png' + target_img = Image.open(target) + + # some versions of freetype have different horizontal spacing. + # setting a tight epsilon, I'm showing the original test failure + # at epsilon = ~38. + assert_image_similar(im, target_img,.5) + + +def test_rotated_transposed_font(): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" + font = ImageFont.truetype(font_path, font_size) + + orientation = Image.ROTATE_90 + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) + + # Original font + draw.setfont(font) + box_size_a = draw.textsize(word) + + # Rotated font + draw.setfont(transposed_font) + box_size_b = draw.textsize(word) + + # Check (w,h) of box a is (h,w) of box b + assert_equal(box_size_a[0], box_size_b[1]) + assert_equal(box_size_a[1], box_size_b[0]) + + +def test_unrotated_transposed_font(): + img_grey = Image.new("L", (100, 100)) + draw = ImageDraw.Draw(img_grey) + word = "testing" + font = ImageFont.truetype(font_path, font_size) + + orientation = None + transposed_font = ImageFont.TransposedFont(font, orientation=orientation) + + # Original font + draw.setfont(font) + box_size_a = draw.textsize(word) + + # Rotated font + draw.setfont(transposed_font) + box_size_b = draw.textsize(word) + + # Check boxes a and b are same size + assert_equal(box_size_a, box_size_b) + + diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py new file mode 100644 index 000000000..67ff71960 --- /dev/null +++ b/Tests/test_imagegrab.py @@ -0,0 +1,13 @@ +from tester import * + +from PIL import Image +try: + from PIL import ImageGrab +except ImportError as v: + skip(v) + +def test_grab(): + im = ImageGrab.grab() + assert_image(im, im.mode, im.size) + + diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py new file mode 100644 index 000000000..eaeb711ba --- /dev/null +++ b/Tests/test_imagemath.py @@ -0,0 +1,62 @@ +from tester import * + +from PIL import Image +from PIL import ImageMath + +def pixel(im): + if hasattr(im, "im"): + return "%s %r" % (im.mode, im.getpixel((0, 0))) + else: + if isinstance(im, type(0)): + return int(im) # hack to deal with booleans + print(im) + +A = Image.new("L", (1, 1), 1) +B = Image.new("L", (1, 1), 2) +F = Image.new("F", (1, 1), 3) +I = Image.new("I", (1, 1), 4) + +images = {"A": A, "B": B, "F": F, "I": I} + +def test_sanity(): + assert_equal(ImageMath.eval("1"), 1) + assert_equal(ImageMath.eval("1+A", A=2), 3) + assert_equal(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") + assert_equal(pixel(ImageMath.eval("A+B", images)), "I 3") + assert_equal(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") + assert_equal(pixel(ImageMath.eval("int(float(A)+B)", images)), "I 3") + +def test_ops(): + + assert_equal(pixel(ImageMath.eval("-A", images)), "I -1") + assert_equal(pixel(ImageMath.eval("+B", images)), "L 2") + + assert_equal(pixel(ImageMath.eval("A+B", images)), "I 3") + assert_equal(pixel(ImageMath.eval("A-B", images)), "I -1") + assert_equal(pixel(ImageMath.eval("A*B", images)), "I 2") + assert_equal(pixel(ImageMath.eval("A/B", images)), "I 0") + assert_equal(pixel(ImageMath.eval("B**2", images)), "I 4") + assert_equal(pixel(ImageMath.eval("B**33", images)), "I 2147483647") + + assert_equal(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") + assert_equal(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") + assert_equal(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") + assert_equal(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") + assert_equal(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") + assert_equal(pixel(ImageMath.eval("float(B)**33", images)), "F 8589934592.0") + +def test_logical(): + assert_equal(pixel(ImageMath.eval("not A", images)), 0) + assert_equal(pixel(ImageMath.eval("A and B", images)), "L 2") + assert_equal(pixel(ImageMath.eval("A or B", images)), "L 1") + +def test_convert(): + assert_equal(pixel(ImageMath.eval("convert(A+B, 'L')", images)), "L 3") + assert_equal(pixel(ImageMath.eval("convert(A+B, '1')", images)), "1 0") + assert_equal(pixel(ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") + +def test_compare(): + assert_equal(pixel(ImageMath.eval("min(A, B)", images)), "I 1") + assert_equal(pixel(ImageMath.eval("max(A, B)", images)), "I 2") + assert_equal(pixel(ImageMath.eval("A == 1", images)), "I 1") + assert_equal(pixel(ImageMath.eval("A == 2", images)), "I 0") diff --git a/Tests/test_imagemode.py b/Tests/test_imagemode.py new file mode 100644 index 000000000..54b04435f --- /dev/null +++ b/Tests/test_imagemode.py @@ -0,0 +1,23 @@ +from tester import * + +from PIL import Image +from PIL import ImageMode + +ImageMode.getmode("1") +ImageMode.getmode("L") +ImageMode.getmode("P") +ImageMode.getmode("RGB") +ImageMode.getmode("I") +ImageMode.getmode("F") + +m = ImageMode.getmode("1") +assert_equal(m.mode, "1") +assert_equal(m.bands, ("1",)) +assert_equal(m.basemode, "L") +assert_equal(m.basetype, "L") + +m = ImageMode.getmode("RGB") +assert_equal(m.mode, "RGB") +assert_equal(m.bands, ("R", "G", "B")) +assert_equal(m.basemode, "RGB") +assert_equal(m.basetype, "L") diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py new file mode 100644 index 000000000..8ed5ccefa --- /dev/null +++ b/Tests/test_imageops.py @@ -0,0 +1,81 @@ +from tester import * + +from PIL import Image +from PIL import ImageOps + +class Deformer: + def getmesh(self, im): + x, y = im.size + return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] + +deformer = Deformer() + +def test_sanity(): + + ImageOps.autocontrast(lena("L")) + ImageOps.autocontrast(lena("RGB")) + + ImageOps.autocontrast(lena("L"), cutoff=10) + ImageOps.autocontrast(lena("L"), ignore=[0, 255]) + + ImageOps.colorize(lena("L"), (0, 0, 0), (255, 255, 255)) + ImageOps.colorize(lena("L"), "black", "white") + + ImageOps.crop(lena("L"), 1) + ImageOps.crop(lena("RGB"), 1) + + ImageOps.deform(lena("L"), deformer) + ImageOps.deform(lena("RGB"), deformer) + + ImageOps.equalize(lena("L")) + ImageOps.equalize(lena("RGB")) + + ImageOps.expand(lena("L"), 1) + ImageOps.expand(lena("RGB"), 1) + ImageOps.expand(lena("L"), 2, "blue") + ImageOps.expand(lena("RGB"), 2, "blue") + + ImageOps.fit(lena("L"), (128, 128)) + ImageOps.fit(lena("RGB"), (128, 128)) + + ImageOps.flip(lena("L")) + ImageOps.flip(lena("RGB")) + + ImageOps.grayscale(lena("L")) + ImageOps.grayscale(lena("RGB")) + + ImageOps.invert(lena("L")) + ImageOps.invert(lena("RGB")) + + ImageOps.mirror(lena("L")) + ImageOps.mirror(lena("RGB")) + + ImageOps.posterize(lena("L"), 4) + ImageOps.posterize(lena("RGB"), 4) + + ImageOps.solarize(lena("L")) + ImageOps.solarize(lena("RGB")) + + success() + +def test_1pxfit(): + # Division by zero in equalize if image is 1 pixel high + newimg = ImageOps.fit(lena("RGB").resize((1,1)), (35,35)) + assert_equal(newimg.size,(35,35)) + + newimg = ImageOps.fit(lena("RGB").resize((1,100)), (35,35)) + assert_equal(newimg.size,(35,35)) + + newimg = ImageOps.fit(lena("RGB").resize((100,1)), (35,35)) + assert_equal(newimg.size,(35,35)) + +def test_pil163(): + # Division by zero in equalize if < 255 pixels in image (@PIL163) + + i = lena("RGB").resize((15, 16)) + + ImageOps.equalize(i.convert("L")) + ImageOps.equalize(i.convert("P")) + ImageOps.equalize(i.convert("RGB")) + + success() diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py new file mode 100644 index 000000000..99ec005c8 --- /dev/null +++ b/Tests/test_imageshow.py @@ -0,0 +1,6 @@ +from tester import * + +from PIL import Image +from PIL import ImageShow + +success() diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py new file mode 100644 index 000000000..02a461e22 --- /dev/null +++ b/Tests/test_imagestat.py @@ -0,0 +1,52 @@ +from tester import * + +from PIL import Image +from PIL import ImageStat + +def test_sanity(): + + im = lena() + + st = ImageStat.Stat(im) + st = ImageStat.Stat(im.histogram()) + st = ImageStat.Stat(im, Image.new("1", im.size, 1)) + + assert_no_exception(lambda: st.extrema) + assert_no_exception(lambda: st.sum) + assert_no_exception(lambda: st.mean) + assert_no_exception(lambda: st.median) + assert_no_exception(lambda: st.rms) + assert_no_exception(lambda: st.sum2) + assert_no_exception(lambda: st.var) + assert_no_exception(lambda: st.stddev) + assert_exception(AttributeError, lambda: st.spam) + + assert_exception(TypeError, lambda: ImageStat.Stat(1)) + +def test_lena(): + + im = lena() + + st = ImageStat.Stat(im) + + # verify a few values + assert_equal(st.extrema[0], (61, 255)) + assert_equal(st.median[0], 197) + assert_equal(st.sum[0], 2954416) + assert_equal(st.sum[1], 2027250) + assert_equal(st.sum[2], 1727331) + +def test_constant(): + + im = Image.new("L", (128, 128), 128) + + st = ImageStat.Stat(im) + + assert_equal(st.extrema[0], (128, 128)) + assert_equal(st.sum[0], 128**3) + assert_equal(st.sum2[0], 128**4) + assert_equal(st.mean[0], 128) + assert_equal(st.median[0], 128) + assert_equal(st.rms[0], 128) + assert_equal(st.var[0], 0) + assert_equal(st.stddev[0], 0) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py new file mode 100644 index 000000000..b30971e8f --- /dev/null +++ b/Tests/test_imagetk.py @@ -0,0 +1,9 @@ +from tester import * + +from PIL import Image +try: + from PIL import ImageTk +except (OSError, ImportError) as v: + skip(v) + +success() diff --git a/Tests/test_imagetransform.py b/Tests/test_imagetransform.py new file mode 100644 index 000000000..884e6bb1c --- /dev/null +++ b/Tests/test_imagetransform.py @@ -0,0 +1,18 @@ +from tester import * + +from PIL import Image +from PIL import ImageTransform + +im = Image.new("L", (100, 100)) + +seq = tuple(range(10)) + +def test_sanity(): + transform = ImageTransform.AffineTransform(seq[:6]) + assert_no_exception(lambda: im.transform((100, 100), transform)) + transform = ImageTransform.ExtentTransform(seq[:4]) + assert_no_exception(lambda: im.transform((100, 100), transform)) + transform = ImageTransform.QuadTransform(seq[:8]) + assert_no_exception(lambda: im.transform((100, 100), transform)) + transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) + assert_no_exception(lambda: im.transform((100, 100), transform)) diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py new file mode 100644 index 000000000..268a75b6f --- /dev/null +++ b/Tests/test_imagewin.py @@ -0,0 +1,6 @@ +from tester import * + +from PIL import Image +from PIL import ImageWin + +success() diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py new file mode 100644 index 000000000..93aa694d8 --- /dev/null +++ b/Tests/test_lib_image.py @@ -0,0 +1,30 @@ +from tester import * + +from PIL import Image + +def test_setmode(): + + im = Image.new("L", (1, 1), 255) + im.im.setmode("1") + assert_equal(im.im.getpixel((0, 0)), 255) + im.im.setmode("L") + assert_equal(im.im.getpixel((0, 0)), 255) + + im = Image.new("1", (1, 1), 1) + im.im.setmode("L") + assert_equal(im.im.getpixel((0, 0)), 255) + im.im.setmode("1") + assert_equal(im.im.getpixel((0, 0)), 255) + + im = Image.new("RGB", (1, 1), (1, 2, 3)) + im.im.setmode("RGB") + assert_equal(im.im.getpixel((0, 0)), (1, 2, 3)) + im.im.setmode("RGBA") + assert_equal(im.im.getpixel((0, 0)), (1, 2, 3, 255)) + im.im.setmode("RGBX") + assert_equal(im.im.getpixel((0, 0)), (1, 2, 3, 255)) + im.im.setmode("RGB") + assert_equal(im.im.getpixel((0, 0)), (1, 2, 3)) + + assert_exception(ValueError, lambda: im.im.setmode("L")) + assert_exception(ValueError, lambda: im.im.setmode("RGBABCDE")) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py new file mode 100644 index 000000000..7675348b3 --- /dev/null +++ b/Tests/test_lib_pack.py @@ -0,0 +1,138 @@ +from tester import * + +from PIL import Image + +def pack(): + pass # not yet + +def test_pack(): + + def pack(mode, rawmode): + if len(mode) == 1: + im = Image.new(mode, (1, 1), 1) + else: + im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) + + if py3: + return list(im.tobytes("raw", rawmode)) + else: + return [ord(c) for c in im.tobytes("raw", rawmode)] + + order = 1 if Image._ENDIAN == '<' else -1 + + assert_equal(pack("1", "1"), [128]) + assert_equal(pack("1", "1;I"), [0]) + assert_equal(pack("1", "1;R"), [1]) + assert_equal(pack("1", "1;IR"), [0]) + + assert_equal(pack("L", "L"), [1]) + + assert_equal(pack("I", "I"), [1, 0, 0, 0][::order]) + + assert_equal(pack("F", "F"), [0, 0, 128, 63][::order]) + + assert_equal(pack("LA", "LA"), [1, 2]) + + assert_equal(pack("RGB", "RGB"), [1, 2, 3]) + assert_equal(pack("RGB", "RGB;L"), [1, 2, 3]) + assert_equal(pack("RGB", "BGR"), [3, 2, 1]) + assert_equal(pack("RGB", "RGBX"), [1, 2, 3, 255]) # 255? + assert_equal(pack("RGB", "BGRX"), [3, 2, 1, 0]) + assert_equal(pack("RGB", "XRGB"), [0, 1, 2, 3]) + assert_equal(pack("RGB", "XBGR"), [0, 3, 2, 1]) + + assert_equal(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? + + assert_equal(pack("RGBA", "RGBA"), [1, 2, 3, 4]) + + assert_equal(pack("CMYK", "CMYK"), [1, 2, 3, 4]) + assert_equal(pack("YCbCr", "YCbCr"), [1, 2, 3]) + +def test_unpack(): + + def unpack(mode, rawmode, bytes_): + im = None + + if py3: + data = bytes(range(1,bytes_+1)) + else: + data = ''.join(chr(i) for i in range(1,bytes_+1)) + + im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1) + + return im.getpixel((0, 0)) + + def unpack_1(mode, rawmode, value): + assert mode == "1" + im = None + + if py3: + im = Image.frombytes(mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1) + else: + im = Image.frombytes(mode, (8, 1), chr(value), "raw", rawmode, 0, 1) + + return tuple(im.getdata()) + + X = 255 + + assert_equal(unpack_1("1", "1", 1), (0,0,0,0,0,0,0,X)) + assert_equal(unpack_1("1", "1;I", 1), (X,X,X,X,X,X,X,0)) + assert_equal(unpack_1("1", "1;R", 1), (X,0,0,0,0,0,0,0)) + assert_equal(unpack_1("1", "1;IR", 1), (0,X,X,X,X,X,X,X)) + + assert_equal(unpack_1("1", "1", 170), (X,0,X,0,X,0,X,0)) + assert_equal(unpack_1("1", "1;I", 170), (0,X,0,X,0,X,0,X)) + assert_equal(unpack_1("1", "1;R", 170), (0,X,0,X,0,X,0,X)) + assert_equal(unpack_1("1", "1;IR", 170), (X,0,X,0,X,0,X,0)) + + assert_equal(unpack("L", "L;2", 1), 0) + assert_equal(unpack("L", "L;4", 1), 0) + assert_equal(unpack("L", "L", 1), 1) + assert_equal(unpack("L", "L;I", 1), 254) + assert_equal(unpack("L", "L;R", 1), 128) + assert_equal(unpack("L", "L;16", 2), 2) # little endian + assert_equal(unpack("L", "L;16B", 2), 1) # big endian + + assert_equal(unpack("LA", "LA", 2), (1, 2)) + assert_equal(unpack("LA", "LA;L", 2), (1, 2)) + + assert_equal(unpack("RGB", "RGB", 3), (1, 2, 3)) + assert_equal(unpack("RGB", "RGB;L", 3), (1, 2, 3)) + assert_equal(unpack("RGB", "RGB;R", 3), (128, 64, 192)) + assert_equal(unpack("RGB", "RGB;16B", 6), (1, 3, 5)) # ? + assert_equal(unpack("RGB", "BGR", 3), (3, 2, 1)) + assert_equal(unpack("RGB", "RGB;15", 2), (8, 131, 0)) + assert_equal(unpack("RGB", "BGR;15", 2), (0, 131, 8)) + assert_equal(unpack("RGB", "RGB;16", 2), (8, 64, 0)) + assert_equal(unpack("RGB", "BGR;16", 2), (0, 64, 8)) + assert_equal(unpack("RGB", "RGB;4B", 2), (17, 0, 34)) + + assert_equal(unpack("RGB", "RGBX", 4), (1, 2, 3)) + assert_equal(unpack("RGB", "BGRX", 4), (3, 2, 1)) + assert_equal(unpack("RGB", "XRGB", 4), (2, 3, 4)) + assert_equal(unpack("RGB", "XBGR", 4), (4, 3, 2)) + + assert_equal(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) + assert_equal(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) + assert_equal(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) + assert_equal(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) + assert_equal(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) + assert_equal(unpack("RGBA", "BGRA;15", 2), (0, 131, 8, 0)) + assert_equal(unpack("RGBA", "RGBA;4B", 2), (17, 0, 34, 0)) + + assert_equal(unpack("RGBX", "RGBX", 4), (1, 2, 3, 4)) # 4->255? + assert_equal(unpack("RGBX", "BGRX", 4), (3, 2, 1, 255)) + assert_equal(unpack("RGBX", "XRGB", 4), (2, 3, 4, 255)) + assert_equal(unpack("RGBX", "XBGR", 4), (4, 3, 2, 255)) + assert_equal(unpack("RGBX", "RGB;15", 2), (8, 131, 0, 255)) + assert_equal(unpack("RGBX", "BGR;15", 2), (0, 131, 8, 255)) + assert_equal(unpack("RGBX", "RGB;4B", 2), (17, 0, 34, 255)) + + assert_equal(unpack("CMYK", "CMYK", 4), (1, 2, 3, 4)) + assert_equal(unpack("CMYK", "CMYK;I", 4), (254, 253, 252, 251)) + + assert_exception(ValueError, lambda: unpack("L", "L", 0)) + assert_exception(ValueError, lambda: unpack("RGB", "RGB", 2)) + assert_exception(ValueError, lambda: unpack("CMYK", "CMYK", 2)) + +run() diff --git a/Tests/test_locale.py b/Tests/test_locale.py new file mode 100644 index 000000000..6b2b95201 --- /dev/null +++ b/Tests/test_locale.py @@ -0,0 +1,31 @@ +from tester import * +from PIL import Image + +import locale + +# ref https://github.com/python-pillow/Pillow/issues/272 +## on windows, in polish locale: + +## import locale +## print locale.setlocale(locale.LC_ALL, 'polish') +## import string +## print len(string.whitespace) +## print ord(string.whitespace[6]) + +## Polish_Poland.1250 +## 7 +## 160 + +# one of string.whitespace is not freely convertable into ascii. + +path = "Images/lena.jpg" + +def test_sanity(): + assert_no_exception(lambda: Image.open(path)) + try: + locale.setlocale(locale.LC_ALL, "polish") + except: + skip('polish locale not available') + import string + assert_no_exception(lambda: Image.open(path)) + diff --git a/test/helper.py b/test/helper.py deleted file mode 100644 index 567fc3945..000000000 --- a/test/helper.py +++ /dev/null @@ -1,310 +0,0 @@ -""" -Helper functions. -""" -from __future__ import print_function -import sys - -if sys.version_info[:2] <= (2, 6): - import unittest2 as unittest -else: - import unittest - - -class PillowTestCase(unittest.TestCase): - - def assert_image(self, im, mode, size, msg=None): - if mode is not None: - self.assertEqual( - im.mode, mode, - msg or "got mode %r, expected %r" % (im.mode, mode)) - - if size is not None: - self.assertEqual( - im.size, size, - msg or "got size %r, expected %r" % (im.size, size)) - - def assert_image_equal(self, a, b, msg=None): - self.assertEqual( - a.mode, b.mode, - msg or "got mode %r, expected %r" % (a.mode, b.mode)) - self.assertEqual( - a.size, b.size, - msg or "got size %r, expected %r" % (a.size, b.size)) - self.assertEqual( - a.tobytes(), b.tobytes(), - msg or "got different content") - - def assert_image_similar(self, a, b, epsilon, msg=None): - epsilon = float(epsilon) - self.assertEqual( - a.mode, b.mode, - msg or "got mode %r, expected %r" % (a.mode, b.mode)) - self.assertEqual( - a.size, b.size, - msg or "got size %r, expected %r" % (a.size, b.size)) - - diff = 0 - try: - ord(b'0') - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(ord(abyte)-ord(bbyte)) - except: - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(abyte-bbyte) - ave_diff = float(diff)/(a.size[0]*a.size[1]) - self.assertGreaterEqual( - epsilon, ave_diff, - msg or "average pixel value difference %.4f > epsilon %.4f" % ( - ave_diff, epsilon)) - - def assert_warning(self, warn_class, func): - import warnings - - result = None - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - - # Hopefully trigger a warning. - result = func() - - # Verify some things. - self.assertGreaterEqual(len(w), 1) - found = False - for v in w: - if issubclass(v.category, warn_class): - found = True - break - self.assertTrue(found) - return result - -# # require that deprecation warnings are triggered -# import warnings -# warnings.simplefilter('default') -# # temporarily turn off resource warnings that warn about unclosed -# # files in the test scripts. -# try: -# warnings.filterwarnings("ignore", category=ResourceWarning) -# except NameError: -# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. -# pass - -import sys -py3 = (sys.version_info >= (3, 0)) - -# # some test helpers -# -# _target = None -# _tempfiles = [] -# _logfile = None -# -# -# def success(): -# import sys -# success.count += 1 -# if _logfile: -# print(sys.argv[0], success.count, failure.count, file=_logfile) -# return True -# -# -# def failure(msg=None, frame=None): -# import sys -# import linecache -# failure.count += 1 -# if _target: -# if frame is None: -# frame = sys._getframe() -# while frame.f_globals.get("__name__") != _target.__name__: -# frame = frame.f_back -# location = (frame.f_code.co_filename, frame.f_lineno) -# prefix = "%s:%d: " % location -# line = linecache.getline(*location) -# print(prefix + line.strip() + " failed:") -# if msg: -# print("- " + msg) -# if _logfile: -# print(sys.argv[0], success.count, failure.count, file=_logfile) -# return False -# -# success.count = failure.count = 0 -# -# -# # predicates -# -# def assert_almost_equal(a, b, msg=None, eps=1e-6): -# if abs(a-b) < eps: -# success() -# else: -# failure(msg or "got %r, expected %r" % (a, b)) -# -# -# def assert_deep_equal(a, b, msg=None): -# try: -# if len(a) == len(b): -# if all([x == y for x, y in zip(a, b)]): -# success() -# else: -# failure(msg or "got %s, expected %s" % (a, b)) -# else: -# failure(msg or "got length %s, expected %s" % (len(a), len(b))) -# except: -# assert_equal(a, b, msg) -# -# -# def assert_match(v, pattern, msg=None): -# import re -# if re.match(pattern, v): -# success() -# else: -# failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) - - -# helpers - -def fromstring(data): - from io import BytesIO - from PIL import Image - return Image.open(BytesIO(data)) - - -def tostring(im, format, **options): - from io import BytesIO - out = BytesIO() - im.save(out, format, **options) - return out.getvalue() - - -def lena(mode="RGB", cache={}): - from PIL import Image - im = None - # im = cache.get(mode) - if im is None: - if mode == "RGB": - im = Image.open("Images/lena.ppm") - elif mode == "F": - im = lena("L").convert(mode) - elif mode[:4] == "I;16": - im = lena("I").convert(mode) - else: - im = lena("RGB").convert(mode) - # cache[mode] = im - return im - - -# def assert_image_completely_equal(a, b, msg=None): -# if a != b: -# failure(msg or "images different") -# else: -# success() -# -# -# def tempfile(template, *extra): -# import os -# import os.path -# import sys -# import tempfile -# files = [] -# root = os.path.join(tempfile.gettempdir(), 'pillow-tests') -# try: -# os.mkdir(root) -# except OSError: -# pass -# for temp in (template,) + extra: -# assert temp[:5] in ("temp.", "temp_") -# name = os.path.basename(sys.argv[0]) -# name = temp[:4] + os.path.splitext(name)[0][4:] -# name = name + "_%d" % len(_tempfiles) + temp[4:] -# name = os.path.join(root, name) -# files.append(name) -# _tempfiles.extend(files) -# return files[0] -# -# -# # test runner -# -# def run(): -# global _target, _tests, run -# import sys -# import traceback -# _target = sys.modules["__main__"] -# run = None # no need to run twice -# tests = [] -# for name, value in list(vars(_target).items()): -# if name[:5] == "test_" and type(value) is type(success): -# tests.append((value.__code__.co_firstlineno, name, value)) -# tests.sort() # sort by line -# for lineno, name, func in tests: -# try: -# _tests = [] -# func() -# for func, args in _tests: -# func(*args) -# except: -# t, v, tb = sys.exc_info() -# tb = tb.tb_next -# if tb: -# failure(frame=tb.tb_frame) -# traceback.print_exception(t, v, tb) -# else: -# print("%s:%d: cannot call test function: %s" % ( -# sys.argv[0], lineno, v)) -# failure.count += 1 -# -# -# def yield_test(function, *args): -# # collect delayed/generated tests -# _tests.append((function, args)) -# -# -# def skip(msg=None): -# import os -# print("skip") -# os._exit(0) # don't run exit handlers -# -# -# def ignore(pattern): -# """Tells the driver to ignore messages matching the pattern, for the -# duration of the current test.""" -# print('ignore: %s' % pattern) -# -# -# def _setup(): -# global _logfile -# -# import sys -# if "--coverage" in sys.argv: -# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) -# with warnings.catch_warnings(): -# warnings.simplefilter("ignore") -# import coverage -# cov = coverage.coverage(auto_data=True, include="PIL/*") -# cov.start() -# -# def report(): -# if run: -# run() -# if success.count and not failure.count: -# print("ok") -# # only clean out tempfiles if test passed -# import os -# import os.path -# import tempfile -# for file in _tempfiles: -# try: -# os.remove(file) -# except OSError: -# pass # report? -# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') -# try: -# os.rmdir(temp_root) -# except OSError: -# pass -# -# import atexit -# atexit.register(report) -# -# if "--log" in sys.argv: -# _logfile = open("test.log", "a") -# -# -# _setup() diff --git a/test/test_000_sanity.py b/test/test_000_sanity.py deleted file mode 100644 index 22e582ec3..000000000 --- a/test/test_000_sanity.py +++ /dev/null @@ -1,32 +0,0 @@ -from helper import unittest, PillowTestCase - -import PIL -import PIL.Image - - -class TestSanity(PillowTestCase): - - def test_sanity(self): - - # Make sure we have the binary extension - im = PIL.Image.core.new("L", (100, 100)) - - self.assertEqual(PIL.Image.VERSION[:3], '1.1') - - # Create an image and do stuff with it. - im = PIL.Image.new("1", (100, 100)) - self.assertEqual((im.mode, im.size), ('1', (100, 100))) - self.assertEqual(len(im.tobytes()), 1300) - - # Create images in all remaining major modes. - im = PIL.Image.new("L", (100, 100)) - im = PIL.Image.new("P", (100, 100)) - im = PIL.Image.new("RGB", (100, 100)) - im = PIL.Image.new("I", (100, 100)) - im = PIL.Image.new("F", (100, 100)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_bmp_reference.py b/test/test_bmp_reference.py deleted file mode 100644 index ed012e7c8..000000000 --- a/test/test_bmp_reference.py +++ /dev/null @@ -1,94 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -import os - -base = os.path.join('Tests', 'images', 'bmp') - - -class TestImage(PillowTestCase): - - def get_files(self, d, ext='.bmp'): - return [os.path.join(base, d, f) for f - in os.listdir(os.path.join(base, d)) if ext in f] - - def test_bad(self): - """ These shouldn't crash/dos, but they shouldn't return anything - either """ - for f in self.get_files('b'): - try: - im = Image.open(f) - im.load() - except Exception: # as msg: - pass - # print ("Bad Image %s: %s" %(f,msg)) - - def test_questionable(self): - """ These shouldn't crash/dos, but its not well defined that these - are in spec """ - for f in self.get_files('q'): - try: - im = Image.open(f) - im.load() - except Exception: # as msg: - pass - # print ("Bad Image %s: %s" %(f,msg)) - - def test_good(self): - """ These should all work. There's a set of target files in the - html directory that we can compare against. """ - - # Target files, if they're not just replacing the extension - file_map = {'pal1wb.bmp': 'pal1.png', - 'pal4rle.bmp': 'pal4.png', - 'pal8-0.bmp': 'pal8.png', - 'pal8rle.bmp': 'pal8.png', - 'pal8topdown.bmp': 'pal8.png', - 'pal8nonsquare.bmp': 'pal8nonsquare-v.png', - 'pal8os2.bmp': 'pal8.png', - 'pal8os2sp.bmp': 'pal8.png', - 'pal8os2v2.bmp': 'pal8.png', - 'pal8os2v2-16.bmp': 'pal8.png', - 'pal8v4.bmp': 'pal8.png', - 'pal8v5.bmp': 'pal8.png', - 'rgb16-565pal.bmp': 'rgb16-565.png', - 'rgb24pal.bmp': 'rgb24.png', - 'rgb32.bmp': 'rgb24.png', - 'rgb32bf.bmp': 'rgb24.png' - } - - def get_compare(f): - (head, name) = os.path.split(f) - if name in file_map: - return os.path.join(base, 'html', file_map[name]) - (name, ext) = os.path.splitext(name) - return os.path.join(base, 'html', "%s.png" % name) - - for f in self.get_files('g'): - try: - im = Image.open(f) - im.load() - compare = Image.open(get_compare(f)) - compare.load() - if im.mode == 'P': - # assert image similar doesn't really work - # with paletized image, since the palette might - # be differently ordered for an equivalent image. - im = im.convert('RGBA') - compare = im.convert('RGBA') - self.assert_image_similar(im, compare, 5) - - except Exception as msg: - # there are three here that are unsupported: - unsupported = (os.path.join(base, 'g', 'rgb32bf.bmp'), - os.path.join(base, 'g', 'pal8rle.bmp'), - os.path.join(base, 'g', 'pal4rle.bmp')) - if f not in unsupported: - self.assertTrue( - False, "Unsupported Image %s: %s" % (f, msg)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_file_fli.py b/test/test_file_fli.py deleted file mode 100644 index dd22a58f9..000000000 --- a/test/test_file_fli.py +++ /dev/null @@ -1,23 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - -# sample ppm stream -file = "Images/lena.fli" -data = open(file, "rb").read() - - -class TestImage(PillowTestCase): - - def test_sanity(self): - im = Image.open(file) - im.load() - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "FLI") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_file_icns.py b/test/test_file_icns.py deleted file mode 100644 index 9d838620b..000000000 --- a/test/test_file_icns.py +++ /dev/null @@ -1,74 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - -# sample icon file -file = "Images/pillow.icns" -data = open(file, "rb").read() - -enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') - - -class TestFileIcns(PillowTestCase): - - def test_sanity(self): - # Loading this icon by default should result in the largest size - # (512x512@2x) being loaded - im = Image.open(file) - im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (1024, 1024)) - self.assertEqual(im.format, "ICNS") - - def test_sizes(self): - # Check that we can load all of the sizes, and that the final pixel - # dimensions are as expected - im = Image.open(file) - for w, h, r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open(file) - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, 'RGBA') - self.assertEqual(im2.size, (wr, hr)) - - def test_older_icon(self): - # This icon was made with Icon Composer rather than iconutil; it still - # uses PNG rather than JP2, however (since it was made on 10.9). - im = Image.open('Tests/images/pillow2.icns') - for w, h, r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open('Tests/images/pillow2.icns') - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, 'RGBA') - self.assertEqual(im2.size, (wr, hr)) - - def test_jp2_icon(self): - # This icon was made by using Uli Kusterer's oldiconutil to replace - # the PNG images with JPEG 2000 ones. The advantage of doing this is - # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial - # software therefore does just this. - - # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) - - if not enable_jpeg2k: - return - - im = Image.open('Tests/images/pillow3.icns') - for w, h, r in im.info['sizes']: - wr = w * r - hr = h * r - im2 = Image.open('Tests/images/pillow3.icns') - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, 'RGBA') - self.assertEqual(im2.size, (wr, hr)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_file_ico.py b/test/test_file_ico.py deleted file mode 100644 index dc289e1d2..000000000 --- a/test/test_file_ico.py +++ /dev/null @@ -1,23 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - -# sample ppm stream -file = "Images/lena.ico" -data = open(file, "rb").read() - - -class TestFileIco(PillowTestCase): - - def test_sanity(self): - im = Image.open(file) - im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (16, 16)) - self.assertEqual(im.format, "ICO") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_file_psd.py b/test/test_file_psd.py deleted file mode 100644 index de3d6f33d..000000000 --- a/test/test_file_psd.py +++ /dev/null @@ -1,23 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - -# sample ppm stream -file = "Images/lena.psd" -data = open(file, "rb").read() - - -class TestImagePsd(PillowTestCase): - - def test_sanity(self): - im = Image.open(file) - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PSD") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_file_xpm.py b/test/test_file_xpm.py deleted file mode 100644 index ecbb4137a..000000000 --- a/test/test_file_xpm.py +++ /dev/null @@ -1,23 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - -# sample ppm stream -file = "Images/lena.xpm" -data = open(file, "rb").read() - - -class TestFileXpm(PillowTestCase): - - def test_sanity(self): - im = Image.open(file) - im.load() - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "XPM") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_font_bdf.py b/test/test_font_bdf.py deleted file mode 100644 index b141e6149..000000000 --- a/test/test_font_bdf.py +++ /dev/null @@ -1,22 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import FontFile, BdfFontFile - -filename = "Images/courB08.bdf" - - -class TestImage(PillowTestCase): - - def test_sanity(self): - - file = open(filename, "rb") - font = BdfFontFile.BdfFontFile(file) - - self.assertIsInstance(font, FontFile.FontFile) - self.assertEqual(len([_f for _f in font.glyph if _f]), 190) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_format_lab.py b/test/test_format_lab.py deleted file mode 100644 index 53468db5f..000000000 --- a/test/test_format_lab.py +++ /dev/null @@ -1,48 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - - -class TestFormatLab(PillowTestCase): - - def test_white(self): - i = Image.open('Tests/images/lab.tif') - - i.load() - - self.assertEqual(i.mode, 'LAB') - - self.assertEqual(i.getbands(), ('L', 'A', 'B')) - - k = i.getpixel((0, 0)) - self.assertEqual(k, (255, 128, 128)) - - L = i.getdata(0) - a = i.getdata(1) - b = i.getdata(2) - - self.assertEqual(list(L), [255]*100) - self.assertEqual(list(a), [128]*100) - self.assertEqual(list(b), [128]*100) - - def test_green(self): - # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS - # == RGB: 0, 152, 117 - i = Image.open('Tests/images/lab-green.tif') - - k = i.getpixel((0, 0)) - self.assertEqual(k, (128, 28, 128)) - - def test_red(self): - # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS - # == RGB: 255, 0, 124 - i = Image.open('Tests/images/lab-red.tif') - - k = i.getpixel((0, 0)) - self.assertEqual(k, (128, 228, 128)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_array.py b/test/test_image_array.py deleted file mode 100644 index a0f5f29e1..000000000 --- a/test/test_image_array.py +++ /dev/null @@ -1,46 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - -im = lena().resize((128, 100)) - - -class TestImageCrop(PillowTestCase): - - def test_toarray(self): - def test(mode): - ai = im.convert(mode).__array_interface__ - return ai["shape"], ai["typestr"], len(ai["data"]) - # self.assertEqual(test("1"), ((100, 128), '|b1', 1600)) - self.assertEqual(test("L"), ((100, 128), '|u1', 12800)) - - # FIXME: wrong? - self.assertEqual(test("I"), ((100, 128), Image._ENDIAN + 'i4', 51200)) - # FIXME: wrong? - self.assertEqual(test("F"), ((100, 128), Image._ENDIAN + 'f4', 51200)) - - self.assertEqual(test("RGB"), ((100, 128, 3), '|u1', 38400)) - self.assertEqual(test("RGBA"), ((100, 128, 4), '|u1', 51200)) - self.assertEqual(test("RGBX"), ((100, 128, 4), '|u1', 51200)) - - def test_fromarray(self): - def test(mode): - i = im.convert(mode) - a = i.__array_interface__ - a["strides"] = 1 # pretend it's non-contigous - i.__array_interface__ = a # patch in new version of attribute - out = Image.fromarray(i) - return out.mode, out.size, list(i.getdata()) == list(out.getdata()) - # self.assertEqual(test("1"), ("1", (128, 100), True)) - self.assertEqual(test("L"), ("L", (128, 100), True)) - self.assertEqual(test("I"), ("I", (128, 100), True)) - self.assertEqual(test("F"), ("F", (128, 100), True)) - self.assertEqual(test("RGB"), ("RGB", (128, 100), True)) - self.assertEqual(test("RGBA"), ("RGBA", (128, 100), True)) - self.assertEqual(test("RGBX"), ("RGBA", (128, 100), True)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_copy.py b/test/test_image_copy.py deleted file mode 100644 index a7882db94..000000000 --- a/test/test_image_copy.py +++ /dev/null @@ -1,20 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImageCopy(PillowTestCase): - - def test_copy(self): - def copy(mode): - im = lena(mode) - out = im.copy() - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) - for mode in "1", "P", "L", "RGB", "I", "F": - copy(mode) - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_crop.py b/test/test_image_crop.py deleted file mode 100644 index da93fe7c8..000000000 --- a/test/test_image_crop.py +++ /dev/null @@ -1,59 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImageCrop(PillowTestCase): - - def test_crop(self): - def crop(mode): - out = lena(mode).crop((50, 50, 100, 100)) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, (50, 50)) - for mode in "1", "P", "L", "RGB", "I", "F": - crop(mode) - - def test_wide_crop(self): - - def crop(*bbox): - i = im.crop(bbox) - h = i.histogram() - while h and not h[-1]: - del h[-1] - return tuple(h) - - im = Image.new("L", (100, 100), 1) - - self.assertEqual(crop(0, 0, 100, 100), (0, 10000)) - self.assertEqual(crop(25, 25, 75, 75), (0, 2500)) - - # sides - self.assertEqual(crop(-25, 0, 25, 50), (1250, 1250)) - self.assertEqual(crop(0, -25, 50, 25), (1250, 1250)) - self.assertEqual(crop(75, 0, 125, 50), (1250, 1250)) - self.assertEqual(crop(0, 75, 50, 125), (1250, 1250)) - - self.assertEqual(crop(-25, 25, 125, 75), (2500, 5000)) - self.assertEqual(crop(25, -25, 75, 125), (2500, 5000)) - - # corners - self.assertEqual(crop(-25, -25, 25, 25), (1875, 625)) - self.assertEqual(crop(75, -25, 125, 25), (1875, 625)) - self.assertEqual(crop(75, 75, 125, 125), (1875, 625)) - self.assertEqual(crop(-25, 75, 25, 125), (1875, 625)) - - def test_negative_crop(self): - # Check negative crop size (@PIL171) - - im = Image.new("L", (512, 512)) - im = im.crop((400, 400, 200, 200)) - - self.assertEqual(im.size, (0, 0)) - self.assertEqual(len(im.getdata()), 0) - self.assertRaises(IndexError, lambda: im.getdata()[0]) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_filter.py b/test/test_image_filter.py deleted file mode 100644 index 4a85b0a2e..000000000 --- a/test/test_image_filter.py +++ /dev/null @@ -1,91 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image -from PIL import ImageFilter - - -class TestImageFilter(PillowTestCase): - - def test_sanity(self): - - def filter(filter): - im = lena("L") - out = im.filter(filter) - self.assertEqual(out.mode, im.mode) - self.assertEqual(out.size, im.size) - - filter(ImageFilter.BLUR) - filter(ImageFilter.CONTOUR) - filter(ImageFilter.DETAIL) - filter(ImageFilter.EDGE_ENHANCE) - filter(ImageFilter.EDGE_ENHANCE_MORE) - filter(ImageFilter.EMBOSS) - filter(ImageFilter.FIND_EDGES) - filter(ImageFilter.SMOOTH) - filter(ImageFilter.SMOOTH_MORE) - filter(ImageFilter.SHARPEN) - filter(ImageFilter.MaxFilter) - filter(ImageFilter.MedianFilter) - filter(ImageFilter.MinFilter) - filter(ImageFilter.ModeFilter) - filter(ImageFilter.Kernel((3, 3), list(range(9)))) - - self.assertRaises(TypeError, lambda: filter("hello")) - - def test_crash(self): - - # crashes on small images - im = Image.new("RGB", (1, 1)) - im.filter(ImageFilter.SMOOTH) - - im = Image.new("RGB", (2, 2)) - im.filter(ImageFilter.SMOOTH) - - im = Image.new("RGB", (3, 3)) - im.filter(ImageFilter.SMOOTH) - - def test_modefilter(self): - - def modefilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 - mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - return mod, mod2 - - self.assertEqual(modefilter("1"), (4, 0)) - self.assertEqual(modefilter("L"), (4, 0)) - self.assertEqual(modefilter("P"), (4, 0)) - self.assertEqual(modefilter("RGB"), ((4, 0, 0), (0, 0, 0))) - - def test_rankfilter(self): - - def rankfilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - min = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) - med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) - max = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) - return min, med, max - - self.assertEqual(rankfilter("1"), (0, 4, 8)) - self.assertEqual(rankfilter("L"), (0, 4, 8)) - self.assertRaises(ValueError, lambda: rankfilter("P")) - self.assertEqual(rankfilter("RGB"), ((0, 0, 0), (4, 0, 0), (8, 0, 0))) - self.assertEqual(rankfilter("I"), (0, 4, 8)) - self.assertEqual(rankfilter("F"), (0.0, 4.0, 8.0)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_frombytes.py b/test/test_image_frombytes.py deleted file mode 100644 index aad8046a1..000000000 --- a/test/test_image_frombytes.py +++ /dev/null @@ -1,18 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImageFromBytes(PillowTestCase): - - def test_sanity(self): - im1 = lena() - im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) - - self.assert_image_equal(im1, im2) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getbands.py b/test/test_image_getbands.py deleted file mode 100644 index e803abb02..000000000 --- a/test/test_image_getbands.py +++ /dev/null @@ -1,26 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - - -class TestImageGetBands(PillowTestCase): - - def test_getbands(self): - self.assertEqual(Image.new("1", (1, 1)).getbands(), ("1",)) - self.assertEqual(Image.new("L", (1, 1)).getbands(), ("L",)) - self.assertEqual(Image.new("I", (1, 1)).getbands(), ("I",)) - self.assertEqual(Image.new("F", (1, 1)).getbands(), ("F",)) - self.assertEqual(Image.new("P", (1, 1)).getbands(), ("P",)) - self.assertEqual(Image.new("RGB", (1, 1)).getbands(), ("R", "G", "B")) - self.assertEqual( - Image.new("RGBA", (1, 1)).getbands(), ("R", "G", "B", "A")) - self.assertEqual( - Image.new("CMYK", (1, 1)).getbands(), ("C", "M", "Y", "K")) - self.assertEqual( - Image.new("YCbCr", (1, 1)).getbands(), ("Y", "Cb", "Cr")) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getbbox.py b/test/test_image_getbbox.py deleted file mode 100644 index 83a6a3dec..000000000 --- a/test/test_image_getbbox.py +++ /dev/null @@ -1,45 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImage(PillowTestCase): - - def test_sanity(self): - - bbox = lena().getbbox() - self.assertIsInstance(bbox, tuple) - - def test_bbox(self): - - # 8-bit mode - im = Image.new("L", (100, 100), 0) - self.assertEqual(im.getbbox(), None) - - im.paste(255, (10, 25, 90, 75)) - self.assertEqual(im.getbbox(), (10, 25, 90, 75)) - - im.paste(255, (25, 10, 75, 90)) - self.assertEqual(im.getbbox(), (10, 10, 90, 90)) - - im.paste(255, (-10, -10, 110, 110)) - self.assertEqual(im.getbbox(), (0, 0, 100, 100)) - - # 32-bit mode - im = Image.new("RGB", (100, 100), 0) - self.assertEqual(im.getbbox(), None) - - im.paste(255, (10, 25, 90, 75)) - self.assertEqual(im.getbbox(), (10, 25, 90, 75)) - - im.paste(255, (25, 10, 75, 90)) - self.assertEqual(im.getbbox(), (10, 10, 90, 90)) - - im.paste(255, (-10, -10, 110, 110)) - self.assertEqual(im.getbbox(), (0, 0, 100, 100)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getcolors.py b/test/test_image_getcolors.py deleted file mode 100644 index cfc827b28..000000000 --- a/test/test_image_getcolors.py +++ /dev/null @@ -1,74 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImage(PillowTestCase): - - def test_getcolors(self): - - def getcolors(mode, limit=None): - im = lena(mode) - if limit: - colors = im.getcolors(limit) - else: - colors = im.getcolors() - if colors: - return len(colors) - return None - - self.assertEqual(getcolors("1"), 2) - self.assertEqual(getcolors("L"), 193) - self.assertEqual(getcolors("I"), 193) - self.assertEqual(getcolors("F"), 193) - self.assertEqual(getcolors("P"), 54) # fixed palette - self.assertEqual(getcolors("RGB"), None) - self.assertEqual(getcolors("RGBA"), None) - self.assertEqual(getcolors("CMYK"), None) - self.assertEqual(getcolors("YCbCr"), None) - - self.assertEqual(getcolors("L", 128), None) - self.assertEqual(getcolors("L", 1024), 193) - - self.assertEqual(getcolors("RGB", 8192), None) - self.assertEqual(getcolors("RGB", 16384), 14836) - self.assertEqual(getcolors("RGB", 100000), 14836) - - self.assertEqual(getcolors("RGBA", 16384), 14836) - self.assertEqual(getcolors("CMYK", 16384), 14836) - self.assertEqual(getcolors("YCbCr", 16384), 11995) - - # -------------------------------------------------------------------- - - def test_pack(self): - # Pack problems for small tables (@PIL209) - - im = lena().quantize(3).convert("RGB") - - expected = [ - (3236, (227, 183, 147)), - (6297, (143, 84, 81)), - (6851, (208, 143, 112))] - - A = im.getcolors(maxcolors=2) - self.assertEqual(A, None) - - A = im.getcolors(maxcolors=3) - A.sort() - self.assertEqual(A, expected) - - A = im.getcolors(maxcolors=4) - A.sort() - self.assertEqual(A, expected) - - A = im.getcolors(maxcolors=8) - A.sort() - self.assertEqual(A, expected) - - A = im.getcolors(maxcolors=16) - A.sort() - self.assertEqual(A, expected) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getextrema.py b/test/test_image_getextrema.py deleted file mode 100644 index af7f7698a..000000000 --- a/test/test_image_getextrema.py +++ /dev/null @@ -1,27 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImageGetExtrema(PillowTestCase): - - def test_extrema(self): - - def extrema(mode): - return lena(mode).getextrema() - - self.assertEqual(extrema("1"), (0, 255)) - self.assertEqual(extrema("L"), (40, 235)) - self.assertEqual(extrema("I"), (40, 235)) - self.assertEqual(extrema("F"), (40.0, 235.0)) - self.assertEqual(extrema("P"), (11, 218)) # fixed palette - self.assertEqual( - extrema("RGB"), ((61, 255), (26, 234), (44, 223))) - self.assertEqual( - extrema("RGBA"), ((61, 255), (26, 234), (44, 223), (255, 255))) - self.assertEqual( - extrema("CMYK"), ((0, 194), (21, 229), (32, 211), (0, 0))) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getim.py b/test/test_image_getim.py deleted file mode 100644 index d498d3923..000000000 --- a/test/test_image_getim.py +++ /dev/null @@ -1,19 +0,0 @@ -from helper import unittest, PillowTestCase, lena, py3 - - -class TestImageGetIm(PillowTestCase): - - def test_sanity(self): - im = lena() - type_repr = repr(type(im.getim())) - - if py3: - self.assertIn("PyCapsule", type_repr) - - self.assertIsInstance(im.im.id, int) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_getpalette.py b/test/test_image_getpalette.py deleted file mode 100644 index 0c399c432..000000000 --- a/test/test_image_getpalette.py +++ /dev/null @@ -1,26 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImageGetPalette(PillowTestCase): - - def test_palette(self): - def palette(mode): - p = lena(mode).getpalette() - if p: - return p[:10] - return None - self.assertEqual(palette("1"), None) - self.assertEqual(palette("L"), None) - self.assertEqual(palette("I"), None) - self.assertEqual(palette("F"), None) - self.assertEqual(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - self.assertEqual(palette("RGB"), None) - self.assertEqual(palette("RGBA"), None) - self.assertEqual(palette("CMYK"), None) - self.assertEqual(palette("YCbCr"), None) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_histogram.py b/test/test_image_histogram.py deleted file mode 100644 index 70f78a1fb..000000000 --- a/test/test_image_histogram.py +++ /dev/null @@ -1,26 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImageHistogram(PillowTestCase): - - def test_histogram(self): - - def histogram(mode): - h = lena(mode).histogram() - return len(h), min(h), max(h) - - self.assertEqual(histogram("1"), (256, 0, 8872)) - self.assertEqual(histogram("L"), (256, 0, 199)) - self.assertEqual(histogram("I"), (256, 0, 199)) - self.assertEqual(histogram("F"), (256, 0, 199)) - self.assertEqual(histogram("P"), (256, 0, 2912)) - self.assertEqual(histogram("RGB"), (768, 0, 285)) - self.assertEqual(histogram("RGBA"), (1024, 0, 16384)) - self.assertEqual(histogram("CMYK"), (1024, 0, 16384)) - self.assertEqual(histogram("YCbCr"), (768, 0, 741)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_load.py b/test/test_image_load.py deleted file mode 100644 index 2001c233a..000000000 --- a/test/test_image_load.py +++ /dev/null @@ -1,35 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - -import os - - -class TestImageLoad(PillowTestCase): - - def test_sanity(self): - - im = lena() - - pix = im.load() - - self.assertEqual(pix[0, 0], (223, 162, 133)) - - def test_close(self): - im = Image.open("Images/lena.gif") - im.close() - self.assertRaises(ValueError, lambda: im.load()) - self.assertRaises(ValueError, lambda: im.getpixel((0, 0))) - - def test_contextmanager(self): - fn = None - with Image.open("Images/lena.gif") as im: - fn = im.fp.fileno() - os.fstat(fn) - - self.assertRaises(OSError, lambda: os.fstat(fn)) - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_mode.py b/test/test_image_mode.py deleted file mode 100644 index d229a27a2..000000000 --- a/test/test_image_mode.py +++ /dev/null @@ -1,36 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImage(PillowTestCase): - - def test_sanity(self): - - im = lena() - im.mode - - def test_properties(self): - def check(mode, *result): - signature = ( - Image.getmodebase(mode), Image.getmodetype(mode), - Image.getmodebands(mode), Image.getmodebandnames(mode), - ) - self.assertEqual(signature, result) - check("1", "L", "L", 1, ("1",)) - check("L", "L", "L", 1, ("L",)) - check("P", "RGB", "L", 1, ("P",)) - check("I", "L", "I", 1, ("I",)) - check("F", "L", "F", 1, ("F",)) - check("RGB", "RGB", "L", 3, ("R", "G", "B")) - check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")) - check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) - check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) - check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")) - check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_offset.py b/test/test_image_offset.py deleted file mode 100644 index bb9b5a38c..000000000 --- a/test/test_image_offset.py +++ /dev/null @@ -1,25 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImage(PillowTestCase): - - def test_offset(self): - - im1 = lena() - - im2 = self.assert_warning(DeprecationWarning, lambda: im1.offset(10)) - self.assertEqual(im1.getpixel((0, 0)), im2.getpixel((10, 10))) - - im2 = self.assert_warning( - DeprecationWarning, lambda: im1.offset(10, 20)) - self.assertEqual(im1.getpixel((0, 0)), im2.getpixel((10, 20))) - - im2 = self.assert_warning( - DeprecationWarning, lambda: im1.offset(20, 20)) - self.assertEqual(im1.getpixel((0, 0)), im2.getpixel((20, 20))) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_putalpha.py b/test/test_image_putalpha.py deleted file mode 100644 index 85c7ac262..000000000 --- a/test/test_image_putalpha.py +++ /dev/null @@ -1,52 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - - -class TestImagePutAlpha(PillowTestCase): - - def test_interface(self): - - im = Image.new("RGBA", (1, 1), (1, 2, 3, 0)) - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 0)) - - im = Image.new("RGBA", (1, 1), (1, 2, 3)) - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 255)) - - im.putalpha(Image.new("L", im.size, 4)) - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) - - im.putalpha(5) - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 5)) - - def test_promote(self): - - im = Image.new("L", (1, 1), 1) - self.assertEqual(im.getpixel((0, 0)), 1) - - im.putalpha(2) - self.assertEqual(im.mode, 'LA') - self.assertEqual(im.getpixel((0, 0)), (1, 2)) - - im = Image.new("RGB", (1, 1), (1, 2, 3)) - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3)) - - im.putalpha(4) - self.assertEqual(im.mode, 'RGBA') - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) - - def test_readonly(self): - - im = Image.new("RGB", (1, 1), (1, 2, 3)) - im.readonly = 1 - - im.putalpha(4) - self.assertFalse(im.readonly) - self.assertEqual(im.mode, 'RGBA') - self.assertEqual(im.getpixel((0, 0)), (1, 2, 3, 4)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_putdata.py b/test/test_image_putdata.py deleted file mode 100644 index c7c3115aa..000000000 --- a/test/test_image_putdata.py +++ /dev/null @@ -1,48 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -import sys - -from PIL import Image - - -class TestImagePutData(PillowTestCase): - - def test_sanity(self): - - im1 = lena() - - data = list(im1.getdata()) - - im2 = Image.new(im1.mode, im1.size, 0) - im2.putdata(data) - - self.assert_image_equal(im1, im2) - - # readonly - im2 = Image.new(im1.mode, im2.size, 0) - im2.readonly = 1 - im2.putdata(data) - - self.assertFalse(im2.readonly) - self.assert_image_equal(im1, im2) - - def test_long_integers(self): - # see bug-200802-systemerror - def put(value): - im = Image.new("RGBA", (1, 1)) - im.putdata([value]) - return im.getpixel((0, 0)) - self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) - self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) - self.assertEqual(put(-1), (255, 255, 255, 255)) - self.assertEqual(put(-1), (255, 255, 255, 255)) - if sys.maxsize > 2**32: - self.assertEqual(put(sys.maxsize), (255, 255, 255, 255)) - else: - self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_putpalette.py b/test/test_image_putpalette.py deleted file mode 100644 index b77dcbf00..000000000 --- a/test/test_image_putpalette.py +++ /dev/null @@ -1,36 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import ImagePalette - - -class TestImage(PillowTestCase): - - def test_putpalette(self): - def palette(mode): - im = lena(mode).copy() - im.putpalette(list(range(256))*3) - p = im.getpalette() - if p: - return im.mode, p[:10] - return im.mode - self.assertRaises(ValueError, lambda: palette("1")) - self.assertEqual(palette("L"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - self.assertEqual(palette("P"), ("P", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) - self.assertRaises(ValueError, lambda: palette("I")) - self.assertRaises(ValueError, lambda: palette("F")) - self.assertRaises(ValueError, lambda: palette("RGB")) - self.assertRaises(ValueError, lambda: palette("RGBA")) - self.assertRaises(ValueError, lambda: palette("YCbCr")) - - def test_imagepalette(self): - im = lena("P") - im.putpalette(ImagePalette.negative()) - im.putpalette(ImagePalette.random()) - im.putpalette(ImagePalette.sepia()) - im.putpalette(ImagePalette.wedge()) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_quantize.py b/test/test_image_quantize.py deleted file mode 100644 index 35f876717..000000000 --- a/test/test_image_quantize.py +++ /dev/null @@ -1,35 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImage(PillowTestCase): - - def test_sanity(self): - im = lena() - - im = im.quantize() - self.assert_image(im, "P", im.size) - - im = lena() - im = im.quantize(palette=lena("P")) - self.assert_image(im, "P", im.size) - - def test_octree_quantize(self): - im = lena() - - im = im.quantize(100, Image.FASTOCTREE) - self.assert_image(im, "P", im.size) - - assert len(im.getcolors()) == 100 - - def test_rgba_quantize(self): - im = lena('RGBA') - im.quantize() - self.assertRaises(Exception, lambda: im.quantize(method=0)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_resize.py b/test/test_image_resize.py deleted file mode 100644 index 6c9932e45..000000000 --- a/test/test_image_resize.py +++ /dev/null @@ -1,19 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImageResize(PillowTestCase): - - def test_resize(self): - def resize(mode, size): - out = lena(mode).resize(size) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, size) - for mode in "1", "P", "L", "RGB", "I", "F": - resize(mode, (100, 100)) - resize(mode, (200, 200)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_rotate.py b/test/test_image_rotate.py deleted file mode 100644 index 531fdd63f..000000000 --- a/test/test_image_rotate.py +++ /dev/null @@ -1,22 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImageRotate(PillowTestCase): - - def test_rotate(self): - def rotate(mode): - im = lena(mode) - out = im.rotate(45) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) # default rotate clips output - out = im.rotate(45, expand=1) - self.assertEqual(out.mode, mode) - self.assertNotEqual(out.size, im.size) - for mode in "1", "P", "L", "RGB", "I", "F": - rotate(mode) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_thumbnail.py b/test/test_image_thumbnail.py deleted file mode 100644 index ee49be43e..000000000 --- a/test/test_image_thumbnail.py +++ /dev/null @@ -1,43 +0,0 @@ -from helper import unittest, PillowTestCase, lena - - -class TestImageThumbnail(PillowTestCase): - - def test_sanity(self): - - im = lena() - im.thumbnail((100, 100)) - - self.assert_image(im, im.mode, (100, 100)) - - def test_aspect(self): - - im = lena() - im.thumbnail((100, 100)) - self.assert_image(im, im.mode, (100, 100)) - - im = lena().resize((128, 256)) - im.thumbnail((100, 100)) - self.assert_image(im, im.mode, (50, 100)) - - im = lena().resize((128, 256)) - im.thumbnail((50, 100)) - self.assert_image(im, im.mode, (50, 100)) - - im = lena().resize((256, 128)) - im.thumbnail((100, 100)) - self.assert_image(im, im.mode, (100, 50)) - - im = lena().resize((256, 128)) - im.thumbnail((100, 50)) - self.assert_image(im, im.mode, (100, 50)) - - im = lena().resize((128, 128)) - im.thumbnail((100, 100)) - self.assert_image(im, im.mode, (100, 100)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_tobitmap.py b/test/test_image_tobitmap.py deleted file mode 100644 index 4e2a16df0..000000000 --- a/test/test_image_tobitmap.py +++ /dev/null @@ -1,22 +0,0 @@ -from helper import unittest, PillowTestCase, lena, fromstring - - -class TestImage(PillowTestCase): - - def test_sanity(self): - - self.assertRaises(ValueError, lambda: lena().tobitmap()) - lena().convert("1").tobitmap() - - im1 = lena().convert("1") - - bitmap = im1.tobitmap() - - self.assertIsInstance(bitmap, bytes) - self.assert_image_equal(im1, fromstring(bitmap)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_tobytes.py b/test/test_image_tobytes.py deleted file mode 100644 index 3be9128c1..000000000 --- a/test/test_image_tobytes.py +++ /dev/null @@ -1,13 +0,0 @@ -from helper import unittest, lena - - -class TestImageToBytes(unittest.TestCase): - - def test_sanity(self): - data = lena().tobytes() - self.assertTrue(isinstance(data, bytes)) - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_image_transform.py b/test/test_image_transform.py deleted file mode 100644 index 6ab186c12..000000000 --- a/test/test_image_transform.py +++ /dev/null @@ -1,125 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - - -class TestImageTransform(PillowTestCase): - - def test_extent(self): - im = lena('RGB') - (w, h) = im.size - transformed = im.transform(im.size, Image.EXTENT, - (0, 0, - w//2, h//2), # ul -> lr - Image.BILINEAR) - - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) - - # undone -- precision? - self.assert_image_similar(transformed, scaled, 10) - - def test_quad(self): - # one simple quad transform, equivalent to scale & crop upper left quad - im = lena('RGB') - (w, h) = im.size - transformed = im.transform(im.size, Image.QUAD, - (0, 0, 0, h//2, - # ul -> ccw around quad: - w//2, h//2, w//2, 0), - Image.BILINEAR) - - scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0, 0, w, h)) - - self.assert_image_equal(transformed, scaled) - - def test_mesh(self): - # this should be a checkerboard of halfsized lenas in ul, lr - im = lena('RGBA') - (w, h) = im.size - transformed = im.transform(im.size, Image.MESH, - [((0, 0, w//2, h//2), # box - (0, 0, 0, h, - w, h, w, 0)), # ul -> ccw around quad - ((w//2, h//2, w, h), # box - (0, 0, 0, h, - w, h, w, 0))], # ul -> ccw around quad - Image.BILINEAR) - - # transformed.save('transformed.png') - - scaled = im.resize((w//2, h//2), Image.BILINEAR) - - checker = Image.new('RGBA', im.size) - checker.paste(scaled, (0, 0)) - checker.paste(scaled, (w//2, h//2)) - - self.assert_image_equal(transformed, checker) - - # now, check to see that the extra area is (0, 0, 0, 0) - blank = Image.new('RGBA', (w//2, h//2), (0, 0, 0, 0)) - - self.assert_image_equal(blank, transformed.crop((w//2, 0, w, h//2))) - self.assert_image_equal(blank, transformed.crop((0, h//2, w//2, h))) - - def _test_alpha_premult(self, op): - # create image with half white, half black, - # with the black half transparent. - # do op, - # there should be no darkness in the white section. - im = Image.new('RGBA', (10, 10), (0, 0, 0, 0)) - im2 = Image.new('RGBA', (5, 10), (255, 255, 255, 255)) - im.paste(im2, (0, 0)) - - im = op(im, (40, 10)) - im_background = Image.new('RGB', (40, 10), (255, 255, 255)) - im_background.paste(im, (0, 0), im) - - hist = im_background.histogram() - self.assertEqual(40*10, hist[-1]) - - def test_alpha_premult_resize(self): - - def op(im, sz): - return im.resize(sz, Image.LINEAR) - - self._test_alpha_premult(op) - - def test_alpha_premult_transform(self): - - def op(im, sz): - (w, h) = im.size - return im.transform(sz, Image.EXTENT, - (0, 0, - w, h), - Image.BILINEAR) - - self._test_alpha_premult(op) - - def test_blank_fill(self): - # attempting to hit - # https://github.com/python-pillow/Pillow/issues/254 reported - # - # issue is that transforms with transparent overflow area - # contained junk from previous images, especially on systems with - # constrained memory. So, attempt to fill up memory with a - # pattern, free it, and then run the mesh test again. Using a 1Mp - # image with 4 bands, for 4 megs of data allocated, x 64. OMM (64 - # bit 12.04 VM with 512 megs available, this fails with Pillow < - # a0eaf06cc5f62a6fb6de556989ac1014ff3348ea - # - # Running by default, but I'd totally understand not doing it in - # the future - - foo = [Image.new('RGBA', (1024, 1024), (a, a, a, a)) - for a in range(1, 65)] - - # Yeah. Watch some JIT optimize this out. - foo = None - - self.test_mesh() - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagechops.py b/test/test_imagechops.py deleted file mode 100644 index ec162d52f..000000000 --- a/test/test_imagechops.py +++ /dev/null @@ -1,74 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image -from PIL import ImageChops - - -class TestImage(PillowTestCase): - - def test_sanity(self): - - im = lena("L") - - ImageChops.constant(im, 128) - ImageChops.duplicate(im) - ImageChops.invert(im) - ImageChops.lighter(im, im) - ImageChops.darker(im, im) - ImageChops.difference(im, im) - ImageChops.multiply(im, im) - ImageChops.screen(im, im) - - ImageChops.add(im, im) - ImageChops.add(im, im, 2.0) - ImageChops.add(im, im, 2.0, 128) - ImageChops.subtract(im, im) - ImageChops.subtract(im, im, 2.0) - ImageChops.subtract(im, im, 2.0, 128) - - ImageChops.add_modulo(im, im) - ImageChops.subtract_modulo(im, im) - - ImageChops.blend(im, im, 0.5) - ImageChops.composite(im, im, im) - - ImageChops.offset(im, 10) - ImageChops.offset(im, 10, 20) - - def test_logical(self): - - def table(op, a, b): - out = [] - for x in (a, b): - imx = Image.new("1", (1, 1), x) - for y in (a, b): - imy = Image.new("1", (1, 1), y) - out.append(op(imx, imy).getpixel((0, 0))) - return tuple(out) - - self.assertEqual( - table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) - self.assertEqual( - table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) - self.assertEqual( - table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) - - self.assertEqual( - table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) - self.assertEqual( - table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) - self.assertEqual( - table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) - - self.assertEqual( - table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) - self.assertEqual( - table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) - self.assertEqual( - table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagecms.py b/test/test_imagecms.py deleted file mode 100644 index 1a31636e8..000000000 --- a/test/test_imagecms.py +++ /dev/null @@ -1,214 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - -try: - from PIL import ImageCms - ImageCms.core.profile_open -except ImportError as v: - # Skipped via setUp() - pass - - -SRGB = "Tests/icc/sRGB.icm" - - -class TestImage(PillowTestCase): - - def setUp(self): - try: - from PIL import ImageCms - except ImportError as v: - self.skipTest(v) - - def test_sanity(self): - - # basic smoke test. - # this mostly follows the cms_test outline. - - v = ImageCms.versions() # should return four strings - self.assertEqual(v[0], '1.0.0 pil') - self.assertEqual(list(map(type, v)), [str, str, str, str]) - - # internal version number - self.assertRegexpMatches(ImageCms.core.littlecms_version, "\d+\.\d+$") - - i = ImageCms.profileToProfile(lena(), SRGB, SRGB) - self.assert_image(i, "RGB", (128, 128)) - - i = lena() - ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) - self.assert_image(i, "RGB", (128, 128)) - - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - i = ImageCms.applyTransform(lena(), t) - self.assert_image(i, "RGB", (128, 128)) - - i = lena() - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - ImageCms.applyTransform(lena(), t, inPlace=True) - self.assert_image(i, "RGB", (128, 128)) - - p = ImageCms.createProfile("sRGB") - o = ImageCms.getOpenProfile(SRGB) - t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") - i = ImageCms.applyTransform(lena(), t) - self.assert_image(i, "RGB", (128, 128)) - - t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") - self.assertEqual(t.inputMode, "RGB") - self.assertEqual(t.outputMode, "RGB") - i = ImageCms.applyTransform(lena(), t) - self.assert_image(i, "RGB", (128, 128)) - - # test PointTransform convenience API - lena().point(t) - - def test_name(self): - # get profile information for file - self.assertEqual( - ImageCms.getProfileName(SRGB).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - def test_info(self): - self.assertEqual( - ImageCms.getProfileInfo(SRGB).splitlines(), [ - 'sRGB IEC61966-2.1', '', - 'Copyright (c) 1998 Hewlett-Packard Company', '']) - - def test_copyright(self): - self.assertEqual( - ImageCms.getProfileCopyright(SRGB).strip(), - 'Copyright (c) 1998 Hewlett-Packard Company') - - def test_manufacturer(self): - self.assertEqual( - ImageCms.getProfileManufacturer(SRGB).strip(), - 'IEC http://www.iec.ch') - - def test_model(self): - self.assertEqual( - ImageCms.getProfileModel(SRGB).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - def test_description(self): - self.assertEqual( - ImageCms.getProfileDescription(SRGB).strip(), - 'sRGB IEC61966-2.1') - - def test_intent(self): - self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0) - self.assertEqual(ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) - - def test_profile_object(self): - # same, using profile object - p = ImageCms.createProfile("sRGB") - # self.assertEqual(ImageCms.getProfileName(p).strip(), - # 'sRGB built-in - (lcms internal)') - # self.assertEqual(ImageCms.getProfileInfo(p).splitlines(), - # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) - self.assertEqual(ImageCms.getDefaultIntent(p), 0) - self.assertEqual(ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) - - def test_extensions(self): - # extensions - from io import BytesIO - i = Image.open("Tests/images/rgb.jpg") - p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) - self.assertEqual( - ImageCms.getProfileName(p).strip(), - 'IEC 61966-2.1 Default RGB colour space - sRGB') - - def test_exceptions(self): - # the procedural pyCMS API uses PyCMSError for all sorts of errors - self.assertRaises( - ImageCms.PyCMSError, - lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) - self.assertRaises( - ImageCms.PyCMSError, - lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) - self.assertRaises( - ImageCms.PyCMSError, - lambda: ImageCms.getProfileName(None)) - self.assertRaises( - ImageCms.PyCMSError, - lambda: ImageCms.isIntentSupported(SRGB, None, None)) - - def test_display_profile(self): - # try fetching the profile for the current display device - ImageCms.get_display_profile() - - def test_lab_color_profile(self): - ImageCms.createProfile("LAB", 5000) - ImageCms.createProfile("LAB", 6500) - - def test_simple_lab(self): - i = Image.new('RGB', (10, 10), (128, 128, 128)) - - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - - i_lab = ImageCms.applyTransform(i, t) - - self.assertEqual(i_lab.mode, 'LAB') - - k = i_lab.getpixel((0, 0)) - # not a linear luminance map. so L != 128: - self.assertEqual(k, (137, 128, 128)) - - L = i_lab.getdata(0) - a = i_lab.getdata(1) - b = i_lab.getdata(2) - - self.assertEqual(list(L), [137] * 100) - self.assertEqual(list(a), [128] * 100) - self.assertEqual(list(b), [128] * 100) - - def test_lab_color(self): - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - # Need to add a type mapping for some PIL type to TYPE_Lab_8 in - # findLCMSType, and have that mapping work back to a PIL mode - # (likely RGB). - i = ImageCms.applyTransform(lena(), t) - self.assert_image(i, "LAB", (128, 128)) - - # i.save('temp.lab.tif') # visually verified vs PS. - - target = Image.open('Tests/images/lena.Lab.tif') - - self.assert_image_similar(i, target, 30) - - def test_lab_srgb(self): - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") - - img = Image.open('Tests/images/lena.Lab.tif') - - img_srgb = ImageCms.applyTransform(img, t) - - # img_srgb.save('temp.srgb.tif') # visually verified vs ps. - - self.assert_image_similar(lena(), img_srgb, 30) - - def test_lab_roundtrip(self): - # check to see if we're at least internally consistent. - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - - t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") - - i = ImageCms.applyTransform(lena(), t) - out = ImageCms.applyTransform(i, t2) - - self.assert_image_similar(lena(), out, 2) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagecolor.py b/test/test_imagecolor.py deleted file mode 100644 index 5d8944852..000000000 --- a/test/test_imagecolor.py +++ /dev/null @@ -1,71 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -from PIL import ImageColor - - -class TestImageColor(PillowTestCase): - - def test_sanity(self): - self.assertEqual((255, 0, 0), ImageColor.getrgb("#f00")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("#ff0000")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(255,0,0)")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(255, 0, 0)")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("rgb(100%,0%,0%)")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("hsl(0, 100%, 50%)")) - self.assertEqual((255, 0, 0, 0), ImageColor.getrgb("rgba(255,0,0,0)")) - self.assertEqual( - (255, 0, 0, 0), ImageColor.getrgb("rgba(255, 0, 0, 0)")) - self.assertEqual((255, 0, 0), ImageColor.getrgb("red")) - - # look for rounding errors (based on code by Tim Hatch) - def test_rounding_errors(self): - - for color in list(ImageColor.colormap.keys()): - expected = Image.new( - "RGB", (1, 1), color).convert("L").getpixel((0, 0)) - actual = Image.new("L", (1, 1), color).getpixel((0, 0)) - self.assertEqual(expected, actual) - - self.assertEqual((0, 0, 0), ImageColor.getcolor("black", "RGB")) - self.assertEqual((255, 255, 255), ImageColor.getcolor("white", "RGB")) - self.assertEqual( - (0, 255, 115), ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGB")) - Image.new("RGB", (1, 1), "white") - - self.assertEqual((0, 0, 0, 255), ImageColor.getcolor("black", "RGBA")) - self.assertEqual( - (255, 255, 255, 255), ImageColor.getcolor("white", "RGBA")) - self.assertEqual( - (0, 255, 115, 33), - ImageColor.getcolor("rgba(0, 255, 115, 33)", "RGBA")) - Image.new("RGBA", (1, 1), "white") - - self.assertEqual(0, ImageColor.getcolor("black", "L")) - self.assertEqual(255, ImageColor.getcolor("white", "L")) - self.assertEqual( - 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "L")) - Image.new("L", (1, 1), "white") - - self.assertEqual(0, ImageColor.getcolor("black", "1")) - self.assertEqual(255, ImageColor.getcolor("white", "1")) - # The following test is wrong, but is current behavior - # The correct result should be 255 due to the mode 1 - self.assertEqual( - 162, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) - # Correct behavior - # self.assertEqual( - # 255, ImageColor.getcolor("rgba(0, 255, 115, 33)", "1")) - Image.new("1", (1, 1), "white") - - self.assertEqual((0, 255), ImageColor.getcolor("black", "LA")) - self.assertEqual((255, 255), ImageColor.getcolor("white", "LA")) - self.assertEqual( - (162, 33), ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA")) - Image.new("LA", (1, 1), "white") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagedraw.py b/test/test_imagedraw.py deleted file mode 100644 index 98876296f..000000000 --- a/test/test_imagedraw.py +++ /dev/null @@ -1,255 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image -from PIL import ImageColor -from PIL import ImageDraw - -import sys - -# Image size -w, h = 100, 100 - -# Bounding box points -x0 = int(w / 4) -x1 = int(x0 * 3) -y0 = int(h / 4) -y1 = int(x0 * 3) - -# Two kinds of bounding box -bbox1 = [(x0, y0), (x1, y1)] -bbox2 = [x0, y0, x1, y1] - -# Two kinds of coordinate sequences -points1 = [(10, 10), (20, 40), (30, 30)] -points2 = [10, 10, 20, 40, 30, 30] - - -class TestImageDraw(PillowTestCase): - - def test_sanity(self): - im = lena("RGB").copy() - - draw = ImageDraw.ImageDraw(im) - draw = ImageDraw.Draw(im) - - draw.ellipse(list(range(4))) - draw.line(list(range(10))) - draw.polygon(list(range(100))) - draw.rectangle(list(range(4))) - - def test_deprecated(self): - im = lena().copy() - - draw = ImageDraw.Draw(im) - - self.assert_warning(DeprecationWarning, lambda: draw.setink(0)) - self.assert_warning(DeprecationWarning, lambda: draw.setfill(0)) - - def helper_arc(self, bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - # FIXME Fill param should be named outline. - draw.arc(bbox, 0, 180) - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_arc.png")) - - def test_arc1(self): - self.helper_arc(bbox1) - - def test_arc2(self): - self.helper_arc(bbox2) - - def test_bitmap(self): - # Arrange - small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.bitmap((10, 10), small) - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_bitmap.png")) - - def helper_chord(self, bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.chord(bbox, 0, 180, fill="red", outline="yellow") - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_chord.png")) - - def test_chord1(self): - self.helper_chord(bbox1) - - def test_chord2(self): - self.helper_chord(bbox2) - - def helper_ellipse(self, bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.ellipse(bbox, fill="green", outline="blue") - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_ellipse.png")) - - def test_ellipse1(self): - self.helper_ellipse(bbox1) - - def test_ellipse2(self): - self.helper_ellipse(bbox2) - - def helper_line(self, points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.line(points1, fill="yellow", width=2) - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_line.png")) - - def test_line1(self): - self.helper_line(points1) - - def test_line2(self): - self.helper_line(points2) - - def helper_pieslice(self, bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.pieslice(bbox, -90, 45, fill="white", outline="blue") - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_pieslice.png")) - - def test_pieslice1(self): - self.helper_pieslice(bbox1) - - def test_pieslice2(self): - self.helper_pieslice(bbox2) - - def helper_point(self, points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.point(points1, fill="yellow") - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_point.png")) - - def test_point1(self): - self.helper_point(points1) - - def test_point2(self): - self.helper_point(points2) - - def helper_polygon(self, points): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.polygon(points1, fill="red", outline="blue") - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_polygon.png")) - - def test_polygon1(self): - self.helper_polygon(points1) - - def test_polygon2(self): - self.helper_polygon(points2) - - def helper_rectangle(self, bbox): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - - # Act - draw.rectangle(bbox, fill="black", outline="green") - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_rectangle.png")) - - def test_rectangle1(self): - self.helper_rectangle(bbox1) - - def test_rectangle2(self): - self.helper_rectangle(bbox2) - - def test_floodfill(self): - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) - - # Act - ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_floodfill.png")) - - @unittest.skipIf(hasattr(sys, 'pypy_version_info'), - "Causes fatal RPython error on PyPy") - def test_floodfill_border(self): - # floodfill() is experimental - - # Arrange - im = Image.new("RGB", (w, h)) - draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) - - # Act - ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), - border=ImageColor.getrgb("black")) - del draw - - # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_floodfill2.png")) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imageenhance.py b/test/test_imageenhance.py deleted file mode 100644 index 433c49cf6..000000000 --- a/test/test_imageenhance.py +++ /dev/null @@ -1,28 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image -from PIL import ImageEnhance - - -class TestImageEnhance(PillowTestCase): - - def test_sanity(self): - - # FIXME: assert_image - # Implicit asserts no exception: - ImageEnhance.Color(lena()).enhance(0.5) - ImageEnhance.Contrast(lena()).enhance(0.5) - ImageEnhance.Brightness(lena()).enhance(0.5) - ImageEnhance.Sharpness(lena()).enhance(0.5) - - def test_crash(self): - - # crashes on small images - im = Image.new("RGB", (1, 1)) - ImageEnhance.Sharpness(im).enhance(0.5) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagefileio.py b/test/test_imagefileio.py deleted file mode 100644 index 32ee0bc5e..000000000 --- a/test/test_imagefileio.py +++ /dev/null @@ -1,33 +0,0 @@ -from helper import unittest, PillowTestCase, lena, tostring - -from PIL import Image -from PIL import ImageFileIO - - -class TestImageFileIo(PillowTestCase): - - def test_fileio(self): - - class DumbFile: - def __init__(self, data): - self.data = data - - def read(self, bytes=None): - assert(bytes is None) - return self.data - - def close(self): - pass - - im1 = lena() - - io = ImageFileIO.ImageFileIO(DumbFile(tostring(im1, "PPM"))) - - im2 = Image.open(io) - self.assert_image_equal(im1, im2) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagefilter.py b/test/test_imagefilter.py deleted file mode 100644 index f7edb409a..000000000 --- a/test/test_imagefilter.py +++ /dev/null @@ -1,37 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import ImageFilter - - -class TestImageFilter(PillowTestCase): - - def test_sanity(self): - # see test_image_filter for more tests - - # Check these run. Exceptions cause failures. - ImageFilter.MaxFilter - ImageFilter.MedianFilter - ImageFilter.MinFilter - ImageFilter.ModeFilter - ImageFilter.Kernel((3, 3), list(range(9))) - ImageFilter.GaussianBlur - ImageFilter.GaussianBlur(5) - ImageFilter.UnsharpMask - ImageFilter.UnsharpMask(10) - - ImageFilter.BLUR - ImageFilter.CONTOUR - ImageFilter.DETAIL - ImageFilter.EDGE_ENHANCE - ImageFilter.EDGE_ENHANCE_MORE - ImageFilter.EMBOSS - ImageFilter.FIND_EDGES - ImageFilter.SMOOTH - ImageFilter.SMOOTH_MORE - ImageFilter.SHARPEN - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagefont.py b/test/test_imagefont.py deleted file mode 100644 index 17cb38cc2..000000000 --- a/test/test_imagefont.py +++ /dev/null @@ -1,145 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -from PIL import ImageDraw -from io import BytesIO -import os - -font_path = "Tests/fonts/FreeMono.ttf" -font_size = 20 - - -try: - from PIL import ImageFont - ImageFont.core.getfont # check if freetype is available - - class TestImageFont(PillowTestCase): - - def test_sanity(self): - self.assertRegexpMatches( - ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") - - def test_font_with_name(self): - ImageFont.truetype(font_path, font_size) - self._render(font_path) - self._clean() - - def _font_as_bytes(self): - with open(font_path, 'rb') as f: - font_bytes = BytesIO(f.read()) - return font_bytes - - def test_font_with_filelike(self): - ImageFont.truetype(self._font_as_bytes(), font_size) - self._render(self._font_as_bytes()) - # Usage note: making two fonts from the same buffer fails. - # shared_bytes = self._font_as_bytes() - # self._render(shared_bytes) - # self.assertRaises(Exception, lambda: _render(shared_bytes)) - self._clean() - - def test_font_with_open_file(self): - with open(font_path, 'rb') as f: - self._render(f) - self._clean() - - def test_font_old_parameters(self): - self.assert_warning( - DeprecationWarning, - lambda: ImageFont.truetype(filename=font_path, size=font_size)) - - def _render(self, font): - txt = "Hello World!" - ttf = ImageFont.truetype(font, font_size) - w, h = ttf.getsize(txt) - img = Image.new("RGB", (256, 64), "white") - d = ImageDraw.Draw(img) - d.text((10, 10), txt, font=ttf, fill='black') - - img.save('font.png') - return img - - def _clean(self): - os.unlink('font.png') - - def test_render_equal(self): - img_path = self._render(font_path) - with open(font_path, 'rb') as f: - font_filelike = BytesIO(f.read()) - img_filelike = self._render(font_filelike) - - self.assert_image_equal(img_path, img_filelike) - self._clean() - - def test_render_multiline(self): - im = Image.new(mode='RGB', size=(300, 100)) - draw = ImageDraw.Draw(im) - ttf = ImageFont.truetype(font_path, font_size) - line_spacing = draw.textsize('A', font=ttf)[1] + 8 - lines = ['hey you', 'you are awesome', 'this looks awkward'] - y = 0 - for line in lines: - draw.text((0, y), line, font=ttf) - y += line_spacing - - target = 'Tests/images/multiline_text.png' - target_img = Image.open(target) - - # some versions of freetype have different horizontal spacing. - # setting a tight epsilon, I'm showing the original test failure - # at epsilon = ~38. - self.assert_image_similar(im, target_img, .5) - - def test_rotated_transposed_font(self): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = ImageFont.truetype(font_path, font_size) - - orientation = Image.ROTATE_90 - transposed_font = ImageFont.TransposedFont( - font, orientation=orientation) - - # Original font - draw.setfont(font) - box_size_a = draw.textsize(word) - - # Rotated font - draw.setfont(transposed_font) - box_size_b = draw.textsize(word) - - # Check (w,h) of box a is (h,w) of box b - self.assertEqual(box_size_a[0], box_size_b[1]) - self.assertEqual(box_size_a[1], box_size_b[0]) - - def test_unrotated_transposed_font(self): - img_grey = Image.new("L", (100, 100)) - draw = ImageDraw.Draw(img_grey) - word = "testing" - font = ImageFont.truetype(font_path, font_size) - - orientation = None - transposed_font = ImageFont.TransposedFont( - font, orientation=orientation) - - # Original font - draw.setfont(font) - box_size_a = draw.textsize(word) - - # Rotated font - draw.setfont(transposed_font) - box_size_b = draw.textsize(word) - - # Check boxes a and b are same size - self.assertEqual(box_size_a, box_size_b) - -except ImportError: - class TestImageFont(PillowTestCase): - def test_skip(self): - self.skipTest("ImportError") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagegrab.py b/test/test_imagegrab.py deleted file mode 100644 index 2275d34a1..000000000 --- a/test/test_imagegrab.py +++ /dev/null @@ -1,25 +0,0 @@ -from helper import unittest, PillowTestCase - -try: - from PIL import ImageGrab - - class TestImageCopy(PillowTestCase): - - def test_grab(self): - im = ImageGrab.grab() - self.assert_image(im, im.mode, im.size) - - def test_grab2(self): - im = ImageGrab.grab() - self.assert_image(im, im.mode, im.size) - -except ImportError: - class TestImageCopy(PillowTestCase): - def test_skip(self): - self.skipTest("ImportError") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagemath.py b/test/test_imagemath.py deleted file mode 100644 index 17d43d25a..000000000 --- a/test/test_imagemath.py +++ /dev/null @@ -1,78 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -from PIL import ImageMath - - -def pixel(im): - if hasattr(im, "im"): - return "%s %r" % (im.mode, im.getpixel((0, 0))) - else: - if isinstance(im, type(0)): - return int(im) # hack to deal with booleans - print(im) - -A = Image.new("L", (1, 1), 1) -B = Image.new("L", (1, 1), 2) -F = Image.new("F", (1, 1), 3) -I = Image.new("I", (1, 1), 4) - -images = {"A": A, "B": B, "F": F, "I": I} - - -class TestImageMath(PillowTestCase): - - def test_sanity(self): - self.assertEqual(ImageMath.eval("1"), 1) - self.assertEqual(ImageMath.eval("1+A", A=2), 3) - self.assertEqual(pixel(ImageMath.eval("A+B", A=A, B=B)), "I 3") - self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") - self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - self.assertEqual(pixel( - ImageMath.eval("int(float(A)+B)", images)), "I 3") - - def test_ops(self): - - self.assertEqual(pixel(ImageMath.eval("-A", images)), "I -1") - self.assertEqual(pixel(ImageMath.eval("+B", images)), "L 2") - - self.assertEqual(pixel(ImageMath.eval("A+B", images)), "I 3") - self.assertEqual(pixel(ImageMath.eval("A-B", images)), "I -1") - self.assertEqual(pixel(ImageMath.eval("A*B", images)), "I 2") - self.assertEqual(pixel(ImageMath.eval("A/B", images)), "I 0") - self.assertEqual(pixel(ImageMath.eval("B**2", images)), "I 4") - self.assertEqual(pixel( - ImageMath.eval("B**33", images)), "I 2147483647") - - self.assertEqual(pixel(ImageMath.eval("float(A)+B", images)), "F 3.0") - self.assertEqual(pixel(ImageMath.eval("float(A)-B", images)), "F -1.0") - self.assertEqual(pixel(ImageMath.eval("float(A)*B", images)), "F 2.0") - self.assertEqual(pixel(ImageMath.eval("float(A)/B", images)), "F 0.5") - self.assertEqual(pixel(ImageMath.eval("float(B)**2", images)), "F 4.0") - self.assertEqual(pixel( - ImageMath.eval("float(B)**33", images)), "F 8589934592.0") - - def test_logical(self): - self.assertEqual(pixel(ImageMath.eval("not A", images)), 0) - self.assertEqual(pixel(ImageMath.eval("A and B", images)), "L 2") - self.assertEqual(pixel(ImageMath.eval("A or B", images)), "L 1") - - def test_convert(self): - self.assertEqual(pixel( - ImageMath.eval("convert(A+B, 'L')", images)), "L 3") - self.assertEqual(pixel( - ImageMath.eval("convert(A+B, '1')", images)), "1 0") - self.assertEqual(pixel( - ImageMath.eval("convert(A+B, 'RGB')", images)), "RGB (3, 3, 3)") - - def test_compare(self): - self.assertEqual(pixel(ImageMath.eval("min(A, B)", images)), "I 1") - self.assertEqual(pixel(ImageMath.eval("max(A, B)", images)), "I 2") - self.assertEqual(pixel(ImageMath.eval("A == 1", images)), "I 1") - self.assertEqual(pixel(ImageMath.eval("A == 2", images)), "I 0") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagemode.py b/test/test_imagemode.py deleted file mode 100644 index 7fb596b46..000000000 --- a/test/test_imagemode.py +++ /dev/null @@ -1,32 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import ImageMode - - -class TestImage(PillowTestCase): - - def test_sanity(self): - ImageMode.getmode("1") - ImageMode.getmode("L") - ImageMode.getmode("P") - ImageMode.getmode("RGB") - ImageMode.getmode("I") - ImageMode.getmode("F") - - m = ImageMode.getmode("1") - self.assertEqual(m.mode, "1") - self.assertEqual(m.bands, ("1",)) - self.assertEqual(m.basemode, "L") - self.assertEqual(m.basetype, "L") - - m = ImageMode.getmode("RGB") - self.assertEqual(m.mode, "RGB") - self.assertEqual(m.bands, ("R", "G", "B")) - self.assertEqual(m.basemode, "RGB") - self.assertEqual(m.basetype, "L") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imageops.py b/test/test_imageops.py deleted file mode 100644 index a4a94ca4d..000000000 --- a/test/test_imageops.py +++ /dev/null @@ -1,85 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import ImageOps - - -class TestImageOps(PillowTestCase): - - class Deformer: - def getmesh(self, im): - x, y = im.size - return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] - - deformer = Deformer() - - def test_sanity(self): - - ImageOps.autocontrast(lena("L")) - ImageOps.autocontrast(lena("RGB")) - - ImageOps.autocontrast(lena("L"), cutoff=10) - ImageOps.autocontrast(lena("L"), ignore=[0, 255]) - - ImageOps.colorize(lena("L"), (0, 0, 0), (255, 255, 255)) - ImageOps.colorize(lena("L"), "black", "white") - - ImageOps.crop(lena("L"), 1) - ImageOps.crop(lena("RGB"), 1) - - ImageOps.deform(lena("L"), self.deformer) - ImageOps.deform(lena("RGB"), self.deformer) - - ImageOps.equalize(lena("L")) - ImageOps.equalize(lena("RGB")) - - ImageOps.expand(lena("L"), 1) - ImageOps.expand(lena("RGB"), 1) - ImageOps.expand(lena("L"), 2, "blue") - ImageOps.expand(lena("RGB"), 2, "blue") - - ImageOps.fit(lena("L"), (128, 128)) - ImageOps.fit(lena("RGB"), (128, 128)) - - ImageOps.flip(lena("L")) - ImageOps.flip(lena("RGB")) - - ImageOps.grayscale(lena("L")) - ImageOps.grayscale(lena("RGB")) - - ImageOps.invert(lena("L")) - ImageOps.invert(lena("RGB")) - - ImageOps.mirror(lena("L")) - ImageOps.mirror(lena("RGB")) - - ImageOps.posterize(lena("L"), 4) - ImageOps.posterize(lena("RGB"), 4) - - ImageOps.solarize(lena("L")) - ImageOps.solarize(lena("RGB")) - - def test_1pxfit(self): - # Division by zero in equalize if image is 1 pixel high - newimg = ImageOps.fit(lena("RGB").resize((1, 1)), (35, 35)) - self.assertEqual(newimg.size, (35, 35)) - - newimg = ImageOps.fit(lena("RGB").resize((1, 100)), (35, 35)) - self.assertEqual(newimg.size, (35, 35)) - - newimg = ImageOps.fit(lena("RGB").resize((100, 1)), (35, 35)) - self.assertEqual(newimg.size, (35, 35)) - - def test_pil163(self): - # Division by zero in equalize if < 255 pixels in image (@PIL163) - - i = lena("RGB").resize((15, 16)) - - ImageOps.equalize(i.convert("L")) - ImageOps.equalize(i.convert("P")) - ImageOps.equalize(i.convert("RGB")) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imageshow.py b/test/test_imageshow.py deleted file mode 100644 index 12594c98f..000000000 --- a/test/test_imageshow.py +++ /dev/null @@ -1,18 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -from PIL import ImageShow - - -class TestImage(PillowTestCase): - - def test_sanity(self): - dir(Image) - dir(ImageShow) - pass - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagestat.py b/test/test_imagestat.py deleted file mode 100644 index 4d30ff023..000000000 --- a/test/test_imagestat.py +++ /dev/null @@ -1,63 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image -from PIL import ImageStat - - -class TestImageStat(PillowTestCase): - - def test_sanity(self): - - im = lena() - - st = ImageStat.Stat(im) - st = ImageStat.Stat(im.histogram()) - st = ImageStat.Stat(im, Image.new("1", im.size, 1)) - - # Check these run. Exceptions will cause failures. - st.extrema - st.sum - st.mean - st.median - st.rms - st.sum2 - st.var - st.stddev - - self.assertRaises(AttributeError, lambda: st.spam) - - self.assertRaises(TypeError, lambda: ImageStat.Stat(1)) - - def test_lena(self): - - im = lena() - - st = ImageStat.Stat(im) - - # verify a few values - self.assertEqual(st.extrema[0], (61, 255)) - self.assertEqual(st.median[0], 197) - self.assertEqual(st.sum[0], 2954416) - self.assertEqual(st.sum[1], 2027250) - self.assertEqual(st.sum[2], 1727331) - - def test_constant(self): - - im = Image.new("L", (128, 128), 128) - - st = ImageStat.Stat(im) - - self.assertEqual(st.extrema[0], (128, 128)) - self.assertEqual(st.sum[0], 128**3) - self.assertEqual(st.sum2[0], 128**4) - self.assertEqual(st.mean[0], 128) - self.assertEqual(st.median[0], 128) - self.assertEqual(st.rms[0], 128) - self.assertEqual(st.var[0], 0) - self.assertEqual(st.stddev[0], 0) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagetk.py b/test/test_imagetk.py deleted file mode 100644 index 87a07e288..000000000 --- a/test/test_imagetk.py +++ /dev/null @@ -1,17 +0,0 @@ -from helper import unittest, PillowTestCase - - -class TestImageTk(PillowTestCase): - - def test_import(self): - try: - from PIL import ImageTk - dir(ImageTk) - except (OSError, ImportError) as v: - self.skipTest(v) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagetransform.py b/test/test_imagetransform.py deleted file mode 100644 index f5741df32..000000000 --- a/test/test_imagetransform.py +++ /dev/null @@ -1,27 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -from PIL import ImageTransform - - -class TestImageTransform(PillowTestCase): - - def test_sanity(self): - im = Image.new("L", (100, 100)) - - seq = tuple(range(10)) - - transform = ImageTransform.AffineTransform(seq[:6]) - im.transform((100, 100), transform) - transform = ImageTransform.ExtentTransform(seq[:4]) - im.transform((100, 100), transform) - transform = ImageTransform.QuadTransform(seq[:8]) - im.transform((100, 100), transform) - transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) - im.transform((100, 100), transform) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_imagewin.py b/test/test_imagewin.py deleted file mode 100644 index 916abc77b..000000000 --- a/test/test_imagewin.py +++ /dev/null @@ -1,18 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image -from PIL import ImageWin - - -class TestImage(PillowTestCase): - - def test_sanity(self): - dir(Image) - dir(ImageWin) - pass - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_lib_image.py b/test/test_lib_image.py deleted file mode 100644 index e0a903b00..000000000 --- a/test/test_lib_image.py +++ /dev/null @@ -1,39 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image - - -class TestSanity(PillowTestCase): - - def test_setmode(self): - - im = Image.new("L", (1, 1), 255) - im.im.setmode("1") - self.assertEqual(im.im.getpixel((0, 0)), 255) - im.im.setmode("L") - self.assertEqual(im.im.getpixel((0, 0)), 255) - - im = Image.new("1", (1, 1), 1) - im.im.setmode("L") - self.assertEqual(im.im.getpixel((0, 0)), 255) - im.im.setmode("1") - self.assertEqual(im.im.getpixel((0, 0)), 255) - - im = Image.new("RGB", (1, 1), (1, 2, 3)) - im.im.setmode("RGB") - self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3)) - im.im.setmode("RGBA") - self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3, 255)) - im.im.setmode("RGBX") - self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3, 255)) - im.im.setmode("RGB") - self.assertEqual(im.im.getpixel((0, 0)), (1, 2, 3)) - - self.assertRaises(ValueError, lambda: im.im.setmode("L")) - self.assertRaises(ValueError, lambda: im.im.setmode("RGBABCDE")) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_lib_pack.py b/test/test_lib_pack.py deleted file mode 100644 index 102835b58..000000000 --- a/test/test_lib_pack.py +++ /dev/null @@ -1,147 +0,0 @@ -from helper import unittest, PillowTestCase, py3 - -from PIL import Image - - -class TestLibPack(PillowTestCase): - - def pack(self): - pass # not yet - - def test_pack(self): - - def pack(mode, rawmode): - if len(mode) == 1: - im = Image.new(mode, (1, 1), 1) - else: - im = Image.new(mode, (1, 1), (1, 2, 3, 4)[:len(mode)]) - - if py3: - return list(im.tobytes("raw", rawmode)) - else: - return [ord(c) for c in im.tobytes("raw", rawmode)] - - order = 1 if Image._ENDIAN == '<' else -1 - - self.assertEqual(pack("1", "1"), [128]) - self.assertEqual(pack("1", "1;I"), [0]) - self.assertEqual(pack("1", "1;R"), [1]) - self.assertEqual(pack("1", "1;IR"), [0]) - - self.assertEqual(pack("L", "L"), [1]) - - self.assertEqual(pack("I", "I"), [1, 0, 0, 0][::order]) - - self.assertEqual(pack("F", "F"), [0, 0, 128, 63][::order]) - - self.assertEqual(pack("LA", "LA"), [1, 2]) - - self.assertEqual(pack("RGB", "RGB"), [1, 2, 3]) - self.assertEqual(pack("RGB", "RGB;L"), [1, 2, 3]) - self.assertEqual(pack("RGB", "BGR"), [3, 2, 1]) - self.assertEqual(pack("RGB", "RGBX"), [1, 2, 3, 255]) # 255? - self.assertEqual(pack("RGB", "BGRX"), [3, 2, 1, 0]) - self.assertEqual(pack("RGB", "XRGB"), [0, 1, 2, 3]) - self.assertEqual(pack("RGB", "XBGR"), [0, 3, 2, 1]) - - self.assertEqual(pack("RGBX", "RGBX"), [1, 2, 3, 4]) # 4->255? - - self.assertEqual(pack("RGBA", "RGBA"), [1, 2, 3, 4]) - - self.assertEqual(pack("CMYK", "CMYK"), [1, 2, 3, 4]) - self.assertEqual(pack("YCbCr", "YCbCr"), [1, 2, 3]) - - def test_unpack(self): - - def unpack(mode, rawmode, bytes_): - im = None - - if py3: - data = bytes(range(1, bytes_+1)) - else: - data = ''.join(chr(i) for i in range(1, bytes_+1)) - - im = Image.frombytes(mode, (1, 1), data, "raw", rawmode, 0, 1) - - return im.getpixel((0, 0)) - - def unpack_1(mode, rawmode, value): - assert mode == "1" - im = None - - if py3: - im = Image.frombytes( - mode, (8, 1), bytes([value]), "raw", rawmode, 0, 1) - else: - im = Image.frombytes( - mode, (8, 1), chr(value), "raw", rawmode, 0, 1) - - return tuple(im.getdata()) - - X = 255 - - self.assertEqual(unpack_1("1", "1", 1), (0, 0, 0, 0, 0, 0, 0, X)) - self.assertEqual(unpack_1("1", "1;I", 1), (X, X, X, X, X, X, X, 0)) - self.assertEqual(unpack_1("1", "1;R", 1), (X, 0, 0, 0, 0, 0, 0, 0)) - self.assertEqual(unpack_1("1", "1;IR", 1), (0, X, X, X, X, X, X, X)) - - self.assertEqual(unpack_1("1", "1", 170), (X, 0, X, 0, X, 0, X, 0)) - self.assertEqual(unpack_1("1", "1;I", 170), (0, X, 0, X, 0, X, 0, X)) - self.assertEqual(unpack_1("1", "1;R", 170), (0, X, 0, X, 0, X, 0, X)) - self.assertEqual(unpack_1("1", "1;IR", 170), (X, 0, X, 0, X, 0, X, 0)) - - self.assertEqual(unpack("L", "L;2", 1), 0) - self.assertEqual(unpack("L", "L;4", 1), 0) - self.assertEqual(unpack("L", "L", 1), 1) - self.assertEqual(unpack("L", "L;I", 1), 254) - self.assertEqual(unpack("L", "L;R", 1), 128) - self.assertEqual(unpack("L", "L;16", 2), 2) # little endian - self.assertEqual(unpack("L", "L;16B", 2), 1) # big endian - - self.assertEqual(unpack("LA", "LA", 2), (1, 2)) - self.assertEqual(unpack("LA", "LA;L", 2), (1, 2)) - - self.assertEqual(unpack("RGB", "RGB", 3), (1, 2, 3)) - self.assertEqual(unpack("RGB", "RGB;L", 3), (1, 2, 3)) - self.assertEqual(unpack("RGB", "RGB;R", 3), (128, 64, 192)) - self.assertEqual(unpack("RGB", "RGB;16B", 6), (1, 3, 5)) # ? - self.assertEqual(unpack("RGB", "BGR", 3), (3, 2, 1)) - self.assertEqual(unpack("RGB", "RGB;15", 2), (8, 131, 0)) - self.assertEqual(unpack("RGB", "BGR;15", 2), (0, 131, 8)) - self.assertEqual(unpack("RGB", "RGB;16", 2), (8, 64, 0)) - self.assertEqual(unpack("RGB", "BGR;16", 2), (0, 64, 8)) - self.assertEqual(unpack("RGB", "RGB;4B", 2), (17, 0, 34)) - - self.assertEqual(unpack("RGB", "RGBX", 4), (1, 2, 3)) - self.assertEqual(unpack("RGB", "BGRX", 4), (3, 2, 1)) - self.assertEqual(unpack("RGB", "XRGB", 4), (2, 3, 4)) - self.assertEqual(unpack("RGB", "XBGR", 4), (4, 3, 2)) - - self.assertEqual(unpack("RGBA", "RGBA", 4), (1, 2, 3, 4)) - self.assertEqual(unpack("RGBA", "BGRA", 4), (3, 2, 1, 4)) - self.assertEqual(unpack("RGBA", "ARGB", 4), (2, 3, 4, 1)) - self.assertEqual(unpack("RGBA", "ABGR", 4), (4, 3, 2, 1)) - self.assertEqual(unpack("RGBA", "RGBA;15", 2), (8, 131, 0, 0)) - self.assertEqual(unpack("RGBA", "BGRA;15", 2), (0, 131, 8, 0)) - self.assertEqual(unpack("RGBA", "RGBA;4B", 2), (17, 0, 34, 0)) - - self.assertEqual(unpack("RGBX", "RGBX", 4), (1, 2, 3, 4)) # 4->255? - self.assertEqual(unpack("RGBX", "BGRX", 4), (3, 2, 1, 255)) - self.assertEqual(unpack("RGBX", "XRGB", 4), (2, 3, 4, 255)) - self.assertEqual(unpack("RGBX", "XBGR", 4), (4, 3, 2, 255)) - self.assertEqual(unpack("RGBX", "RGB;15", 2), (8, 131, 0, 255)) - self.assertEqual(unpack("RGBX", "BGR;15", 2), (0, 131, 8, 255)) - self.assertEqual(unpack("RGBX", "RGB;4B", 2), (17, 0, 34, 255)) - - self.assertEqual(unpack("CMYK", "CMYK", 4), (1, 2, 3, 4)) - self.assertEqual(unpack("CMYK", "CMYK;I", 4), (254, 253, 252, 251)) - - self.assertRaises(ValueError, lambda: unpack("L", "L", 0)) - self.assertRaises(ValueError, lambda: unpack("RGB", "RGB", 2)) - self.assertRaises(ValueError, lambda: unpack("CMYK", "CMYK", 2)) - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/test/test_locale.py b/test/test_locale.py deleted file mode 100644 index 599e46266..000000000 --- a/test/test_locale.py +++ /dev/null @@ -1,39 +0,0 @@ -from helper import unittest, PillowTestCase, lena - -from PIL import Image - -import locale - -# ref https://github.com/python-pillow/Pillow/issues/272 -# on windows, in polish locale: - -# import locale -# print locale.setlocale(locale.LC_ALL, 'polish') -# import string -# print len(string.whitespace) -# print ord(string.whitespace[6]) - -# Polish_Poland.1250 -# 7 -# 160 - -# one of string.whitespace is not freely convertable into ascii. - -path = "Images/lena.jpg" - - -class TestLocale(PillowTestCase): - - def test_sanity(self): - Image.open(path) - try: - locale.setlocale(locale.LC_ALL, "polish") - except: - unittest.skip('Polish locale not available') - Image.open(path) - - -if __name__ == '__main__': - unittest.main() - -# End of file From 6a79d80374e0e1c14601c4e12724e4507d4cd834 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 10 Jun 2014 07:45:42 -0400 Subject: [PATCH 119/488] Revert "Merge pull request #5 from hugovk/unittest1merge" This reverts commit 0940f0b043bb16fc37dc9c34673cfaf1a813cc61, reversing changes made to 07aa1a56bbddd79128a81584eb0ec8f903c5192f. Conflicts: .travis.yml test/helper.py test/test_imagedraw.py --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index fe4f47295..e064ed9ef 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -522,7 +522,7 @@ class Image: """ Closes the file pointer, if possible. - This operation will destroy the image core and release its memory. + This operation will destroy the image core and release it's memory. The image data will be unusable afterward. This function is only required to close images that have not From e4fa84412206120cf71af55b9c3f84d685da65d4 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 10 Jun 2014 08:59:29 -0400 Subject: [PATCH 120/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 71a42e315..40d8ed5cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Use unittest for tests + [hugovk] + - ImageCms fixes [hugovk] From 5ceb3ca875f23fc1c58f0f4b32d48900260404f9 Mon Sep 17 00:00:00 2001 From: James Anderson Date: Mon, 9 Jun 2014 11:39:40 -0700 Subject: [PATCH 121/488] Issue #697. On MSVC 2012 builds is not required. --- libImaging/TiffDecode.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libImaging/TiffDecode.h b/libImaging/TiffDecode.h index 90fe3c9d4..70aa63628 100644 --- a/libImaging/TiffDecode.h +++ b/libImaging/TiffDecode.h @@ -13,11 +13,12 @@ #include #endif -#ifndef _UNISTD_H -#include +#ifndef _MSC_VER + #ifndef _UNISTD_H + #include + #endif #endif - #ifndef min #define min(x,y) (( x > y ) ? y : x ) #define max(x,y) (( x < y ) ? y : x ) From a8e02e093ad8bddb3f33dcbf7c322787f286def7 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 18 Jun 2014 16:08:16 -0700 Subject: [PATCH 122/488] We don't need unistd.h anymore on unix platforms either --- libImaging/TiffDecode.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libImaging/TiffDecode.h b/libImaging/TiffDecode.h index 70aa63628..67d6e82d1 100644 --- a/libImaging/TiffDecode.h +++ b/libImaging/TiffDecode.h @@ -13,12 +13,6 @@ #include #endif -#ifndef _MSC_VER - #ifndef _UNISTD_H - #include - #endif -#endif - #ifndef min #define min(x,y) (( x > y ) ? y : x ) #define max(x,y) (( x < y ) ? y : x ) From 05d8cb5b8fe3e3b89d877c8663b3367b6d939e12 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 18 Jun 2014 16:08:50 -0700 Subject: [PATCH 123/488] Don't carve out exceptions for py2.4 compilers anymore --- libImaging/TiffDecode.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/libImaging/TiffDecode.h b/libImaging/TiffDecode.h index 67d6e82d1..46c940d1b 100644 --- a/libImaging/TiffDecode.h +++ b/libImaging/TiffDecode.h @@ -38,11 +38,10 @@ extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); -#if defined(_MSC_VER) && (_MSC_VER == 1310) -/* VS2003/py2.4 can't use varargs. Skipping trace for now.*/ -#define TRACE(args) -#else - +/* + Trace debugging + legacy, don't enable for python 3.x, unicode issues. +*/ /* #define VA_ARGS(...) __VA_ARGS__ @@ -51,8 +50,5 @@ extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); #define TRACE(args) -#endif /* _MSC_VER */ - - #endif From 6e79cc00ffa630752414d79c145e783f610e8ee0 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 05:55:34 -0700 Subject: [PATCH 124/488] test runner for installed versions of pillow --- test-installed.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 test-installed.py diff --git a/test-installed.py b/test-installed.py new file mode 100755 index 000000000..7f58f4966 --- /dev/null +++ b/test-installed.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +import nose +import os +import sys +import glob + +# monkey with the path, removing the local directory but adding the Tests/ directory +# for helper.py and the other local imports there. + +del(sys.path[0]) +sys.path.insert(0, os.path.abspath('./Tests')) + +# if there's no test selected (mostly) choose a working default. +# Something is required, because if we import the tests from the local +# directory, once again, we've got the non-installed PIL in the way +if len(sys.argv) == 1: + sys.argv.extend(glob.glob('Tests/test*.py')) + +# Make sure that nose doesn't muck with our paths. +if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): + sys.argv.insert(1, '--no-path-adjustment') + +if __name__ == '__main__': + nose.main() From 0b0ec8b40faba37bbd937391aaa0cb1ef294ce9f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 06:06:23 -0700 Subject: [PATCH 125/488] Proper skipping of tests when lcms2 is not installed --- Tests/test_imagecms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index d4432d9be..f3f0791e5 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -18,6 +18,8 @@ class TestImageCms(PillowTestCase): def setUp(self): try: from PIL import ImageCms + # need to hit getattr to trigger the delayed import error + ImageCms.core.profile_open except ImportError as v: self.skipTest(v) From 85693d60d0f7d836d2d02d94e330aaf8d445c044 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 13:21:14 -0700 Subject: [PATCH 126/488] initial py3 compatibility --- PIL/ImageMorph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index a78e341ed..e2c29e850 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -73,9 +73,9 @@ class LutBuilder: self.patterns += patterns def build_default_lut(self): - symbols = ['\0','\1'] + symbols = [0, 1] m = 1 << 4 # pos of current pixel - self.lut = bytearray(''.join([symbols[(i & m)>0] for i in range(LUT_SIZE)])) + self.lut = bytearray([symbols[(i & m)>0] for i in range(LUT_SIZE)]) def get_lut(self): return self.lut @@ -168,7 +168,7 @@ class LutBuilder: for p,r in patterns: if p.match(bitpattern): - self.lut[i] = ['\0','\1'][r] + self.lut[i] = [0, 1][r] return self.lut From a422a4ff4e8330162ecc83abaf9cd43b4e30615a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 13:21:40 -0700 Subject: [PATCH 127/488] ensure files are closed --- PIL/ImageMorph.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index e2c29e850..116b6d756 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -209,14 +209,15 @@ class MorphOp: def get_on_pixels(self, image): """Get a list of all turned on pixels in a binary image - Returns a list of tuples of (x,y) coordinates of all matching pixels.""" return _imagingmorph.get_on_pixels(image.im.id) def load_lut(self, filename): """Load an operator from an mrl file""" - self.lut = bytearray(open(filename,'rb').read()) + with open(filename,'rb') as f: + self.lut = bytearray(f.read()) + if len(self.lut)!= 8192: self.lut = None raise Exception('Wrong size operator file!') @@ -225,7 +226,8 @@ class MorphOp: """Load an operator save mrl file""" if self.lut is None: raise Exception('No operator loaded') - open(filename,'wb').write(self.lut) + with open(filename,'wb') as f: + f.write(self.lut) def set_lut(self, lut): """Set the lut from an external source""" From b95eb3d3d10cab051f252fd50fd3289405c86751 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 14:54:39 -0700 Subject: [PATCH 128/488] Pass bytearray into C layer instead of castin g to string in the Python layer. --- PIL/ImageMorph.py | 4 ++-- _imagingmorph.c | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 116b6d756..7996a48aa 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -195,7 +195,7 @@ class MorphOp: raise Exception('No operator loaded') outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply(str(self.lut), image.im.id, outimage.im.id) + count = _imagingmorph.apply(self.lut, image.im.id, outimage.im.id) return count, outimage def match(self, image): @@ -205,7 +205,7 @@ class MorphOp: if self.lut is None: raise Exception('No operator loaded') - return _imagingmorph.match(str(self.lut), image.im.id) + return _imagingmorph.match(self.lut, image.im.id) def get_on_pixels(self, image): """Get a list of all turned on pixels in a binary image diff --git a/_imagingmorph.c b/_imagingmorph.c index f80124022..68a1db35b 100644 --- a/_imagingmorph.c +++ b/_imagingmorph.c @@ -32,6 +32,7 @@ static PyObject* apply(PyObject *self, PyObject* args) { const char *lut; + PyObject *py_lut; Py_ssize_t lut_len, i0, i1; Imaging imgin, imgout; int width, height; @@ -39,17 +40,25 @@ apply(PyObject *self, PyObject* args) UINT8 **inrows, **outrows; int num_changed_pixels = 0; - if (!PyArg_ParseTuple(args, "s#nn", &lut, &lut_len, &i0, &i1)) { + if (!PyArg_ParseTuple(args, "Onn", &py_lut, &i0, &i1)) { PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); - return NULL; } + if (!PyByteArray_Check(py_lut)) { + PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a byte array"); + return NULL; + } + + lut_len = PyByteArray_Size(py_lut); + if (lut_len < LUT_SIZE) { PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } + lut = PyByteArray_AsString(py_lut); + imgin = (Imaging) i0; imgout = (Imaging) i1; width = imgin->xsize; @@ -130,6 +139,7 @@ static PyObject* match(PyObject *self, PyObject* args) { const char *lut; + PyObject *py_lut; Py_ssize_t lut_len, i0; Imaging imgin; int width, height; @@ -137,17 +147,24 @@ match(PyObject *self, PyObject* args) UINT8 **inrows; PyObject *ret = PyList_New(0); - if (!PyArg_ParseTuple(args, "s#n", &lut, &lut_len, &i0)) { + if (!PyArg_ParseTuple(args, "On", &py_lut, &i0)) { PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); - return NULL; } + if (!PyByteArray_Check(py_lut)) { + PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a byte array"); + return NULL; + } + + lut_len = PyByteArray_Size(py_lut); + if (lut_len < LUT_SIZE) { PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } + lut = PyByteArray_AsString(py_lut); imgin = (Imaging) i0; if (imgin->type != IMAGING_TYPE_UINT8 && From b8834fa3a6204996d8cc8d6ee13c1fa05a65d42e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 14:55:43 -0700 Subject: [PATCH 129/488] Additional tests, tests in functions --- Tests/images/corner.lut | Bin 0 -> 512 bytes Tests/images/dilation4.lut | Bin 0 -> 512 bytes Tests/images/dilation8.lut | Bin 0 -> 512 bytes Tests/images/edge.lut | Bin 0 -> 512 bytes Tests/images/erosion4.lut | Bin 0 -> 512 bytes Tests/images/erosion8.lut | Bin 0 -> 512 bytes Tests/images/morph_a.png | Bin 0 -> 83 bytes Tests/test_imagemorph.py | 189 +++++++++++++++++++++---------------- 8 files changed, 109 insertions(+), 80 deletions(-) create mode 100644 Tests/images/corner.lut create mode 100644 Tests/images/dilation4.lut create mode 100644 Tests/images/dilation8.lut create mode 100644 Tests/images/edge.lut create mode 100644 Tests/images/erosion4.lut create mode 100644 Tests/images/erosion8.lut create mode 100644 Tests/images/morph_a.png diff --git a/Tests/images/corner.lut b/Tests/images/corner.lut new file mode 100644 index 0000000000000000000000000000000000000000..7b0386be38826995dc2bbc5e30c935d4b44cfc28 GIT binary patch literal 512 zcmZQzKn09w09BTO0Va(u4&%e*VKht}Ml%8#AdCwz0wuv}a3})H;nEBgr(8cSmqRSY LFG~&k>Fa+0b@c#X literal 0 HcmV?d00001 diff --git a/Tests/images/dilation4.lut b/Tests/images/dilation4.lut new file mode 100644 index 0000000000000000000000000000000000000000..958b801abf8c5a287dd2eb86df082ae05ef7738f GIT binary patch literal 512 jcmZQzU}R(fVsZgUFD3fPwU=7@D0M%j`l;m)a?Jq%?Kc7O literal 0 HcmV?d00001 diff --git a/Tests/images/dilation8.lut b/Tests/images/dilation8.lut new file mode 100644 index 0000000000000000000000000000000000000000..c3bca868404f9b045ce3377574bb094a54546b7c GIT binary patch literal 512 NcmZQz90eme1ONdZ0ssI2 literal 0 HcmV?d00001 diff --git a/Tests/images/edge.lut b/Tests/images/edge.lut new file mode 100644 index 0000000000000000000000000000000000000000..f7aabf41cf7899928ed7d486020c58807ff8ff29 GIT binary patch literal 512 dcmZQzKn09w09BTO0Zp25R30@1hFJJ8008=b0RR91 literal 0 HcmV?d00001 diff --git a/Tests/images/erosion4.lut b/Tests/images/erosion4.lut new file mode 100644 index 0000000000000000000000000000000000000000..9b6d2933e3a4600af7361acc9367ba24eb131f1c GIT binary patch literal 512 ecmZQz7^;Agk%0k($pw_^C)ZwT>7&&B5d8oQH~>$hmBp4oAa_|EwDNh&2kcv6U2@b41JUl@3UvkER d8a1{z3=B$_8QI>o=(&JYd%F6$taD0e0swo&5_0', - '4:(00. 01. ...)->1']) -count,Aout = mop.apply(A) -assert_equal(count,5) -assert_img_equal_img_string(Aout, -""" -....... -....... -..1.1.. -....... -..1.1.. -....... -....... -""") +def test_corner(): + # Create a corner detector pattern + mop = ImageMorph.MorphOp(patterns = ['1:(... ... ...)->0', + '4:(00. 01. ...)->1']) + count,Aout = mop.apply(A) + assert_equal(count,5) + assert_img_equal_img_string(Aout, + """ + ....... + ....... + ..1.1.. + ....... + ..1.1.. + ....... + ....... + """) -# Test the coordinate counting with the same operator -coords = mop.match(A) -assert_equal(len(coords), 4) -assert_equal(tuple(coords), - ((2,2),(4,2),(2,4),(4,4))) -coords = mop.get_on_pixels(Aout) -assert_equal(len(coords), 4) -assert_equal(tuple(coords), - ((2,2),(4,2),(2,4),(4,4))) + # Test the coordinate counting with the same operator + coords = mop.match(A) + assert_equal(len(coords), 4) + assert_equal(tuple(coords), + ((2,2),(4,2),(2,4),(4,4))) + + coords = mop.get_on_pixels(Aout) + assert_equal(len(coords), 4) + assert_equal(tuple(coords), + ((2,2),(4,2),(2,4),(4,4))) From cf56ccc828654703170b7bab8a1d1faa8014a451 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 19 Jun 2014 15:17:00 -0700 Subject: [PATCH 130/488] Working on python 2.6 --- Tests/test_imagemorph.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 092b7f996..a5e28cd7e 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -4,8 +4,6 @@ from tester import * from PIL import Image from PIL import ImageMorph -import binascii - def img_to_string(im): """Turn a (small) binary image into a string representation""" chars = '.1' @@ -58,7 +56,6 @@ def create_lut(): for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut() - print (binascii.hexlify(lut)) with open('Tests/images/%s.lut' % op, 'wb') as f: f.write(lut) @@ -68,7 +65,7 @@ def test_lut(): lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut() with open('Tests/images/%s.lut' % op , 'rb') as f: - assert_equal(binascii.hexlify(lut), binascii.hexlify(bytearray(f.read()))) + assert_equal(lut, bytearray(f.read())) # Test the named patterns From fcd4c662bfb21a320406c8ea0a5af365113a0597 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jun 2014 00:40:18 -0700 Subject: [PATCH 131/488] Fixed JPEG qtables test --- Tests/test_file_jpeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 74ee8a1c3..d584eef1c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -224,7 +224,7 @@ class TestFileJpeg(PillowTestCase): filename = "Tests/images/junk_jpeg_header.jpg" Image.open(filename) - def test_qtables(): + def test_qtables(self): im = Image.open("Tests/images/lena.jpg") qtables = im.quantization reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) From dfe7ff515f648204a8632738f605488b825c72da Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jun 2014 01:09:59 -0700 Subject: [PATCH 132/488] Additional jpeg qtables tests --- Tests/test_file_jpeg.py | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index d584eef1c..dae1d0019 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -229,8 +229,52 @@ class TestFileJpeg(PillowTestCase): qtables = im.quantization reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) self.assertEqual(im.quantization, reloaded.quantization) + self.assert_image_similar(im, self.roundtrip(im, qtables='web_low'), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables='web_high'), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables='keep'), 30) + #values from wizard.txt in jpeg9-a src package. + standard_l_qtable = [int(s) for s in """ + 16 11 10 16 24 40 51 61 + 12 12 14 19 26 58 60 55 + 14 13 16 24 40 57 69 56 + 14 17 22 29 51 87 80 62 + 18 22 37 56 68 109 103 77 + 24 35 55 64 81 104 113 92 + 49 64 78 87 103 121 120 101 + 72 92 95 98 112 100 103 99 + """.split(None)] + standard_chrominance_qtable= [int(s) for s in """ + 17 18 24 47 99 99 99 99 + 18 21 26 66 99 99 99 99 + 24 26 56 99 99 99 99 99 + 47 66 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + """.split(None)] + # list of qtable lists + self.assert_image_similar(im, + self.roundtrip(im, + qtables=[standard_l_qtable, + standard_chrominance_qtable]), + 30) + # tuple of qtable lists + self.assert_image_similar(im, + self.roundtrip(im, + qtables=(standard_l_qtable, + standard_chrominance_qtable)), + 30) + # dict of qtable lists + self.assert_image_similar(im, + self.roundtrip(im, + qtables={0:standard_l_qtable, + 1:standard_chrominance_qtable}), + 30) + + if __name__ == '__main__': unittest.main() From 06ad0b092abf27482aea467ed8ae0c76d0e2e880 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jun 2014 01:39:42 -0700 Subject: [PATCH 133/488] Added docs for JPEG qtables and subsampling --- docs/handbook/image-file-formats.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index fddc134e6..03e55f35a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -147,6 +147,29 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: If present, indicates that this image should be stored as a progressive JPEG file. +**subsampling** + If present, sets the subsampling for the encoder. + + * ``keep``: Only valid for JPEG files, will retain the original image setting. + * ``4:4:4``, ``4:2:2``, ``4:1:1``: Specific sampling values + * ``-1``: equivalent to ``keep`` + * ``0``: equivalent to ``4:4:4`` + * ``1``: equivalent to ``4:2:2`` + * ``2``: equivalent to ``4:1:1`` + +**qtables** + If present, sets the qtables for the encoder. This is listed as an + advanced option for wizards in the JPEG documentation. Use with + caution. ``qtables`` can be one of several types of values: + + * a string, naming a preset, e.g. ``keep``, ``web_low``, or ``web_high`` + * a list, tuple, or dictionary (with integer keys = + range(len(keys))) of lists of 64 integers. There must be + between 2 and 4 tables. + + .. versionadded:: 2.5.0 + + .. note:: To enable JPEG support, you need to build and install the IJG JPEG library From df3993a67748a9fb57eae41cd8ee0944fdf1f491 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 15:38:49 -0400 Subject: [PATCH 134/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 40d8ed5cd..563c1abd3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Remove unistd.h #include for all platforms + [wiredfool] + - Use unittest for tests [hugovk] From 9f9a9f9e61e306edf01d00b0a55843db25741846 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 15:40:02 -0400 Subject: [PATCH 135/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 563c1abd3..ff5a9ecb9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Support OpenJpeg 2.1 + [wiredfool] + - Remove unistd.h #include for all platforms [wiredfool] From 9ed5b08cb9dd4ae2748b12bc58e8d9b4c9f57b43 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 16:30:41 -0400 Subject: [PATCH 136/488] Update URL http://www.graficaobscura.com/interp/index.html --- PIL/ImageEnhance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImageEnhance.py b/PIL/ImageEnhance.py index 10433343e..f802dc1d3 100644 --- a/PIL/ImageEnhance.py +++ b/PIL/ImageEnhance.py @@ -6,7 +6,7 @@ # # For a background, see "Image Processing By Interpolation and # Extrapolation", Paul Haeberli and Douglas Voorhies. Available -# at http://www.sgi.com/grafica/interp/index.html +# at http://www.graficaobscura.com/interp/index.html # # History: # 1996-03-23 fl Created From 175d68aeeb5277714044b2685064c135b4b7ec3c Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 16:44:52 -0400 Subject: [PATCH 137/488] Update URL; fix typo --- PIL/WalImageFile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/WalImageFile.py b/PIL/WalImageFile.py index a962e8a99..d494bfd58 100644 --- a/PIL/WalImageFile.py +++ b/PIL/WalImageFile.py @@ -14,11 +14,11 @@ # # NOTE: This format cannot be automatically recognized, so the reader -# is not registered for use with Image.open(). To open a WEL file, use +# is not registered for use with Image.open(). To open a WAL file, use # the WalImageFile.open() function instead. # This reader is based on the specification available from: -# http://www.flipcode.com/tutorials/tut_q2levels.shtml +# http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml # and has been tested with a few sample files found using google. from __future__ import print_function From 4470b07c3fee3bd58e60dce20a1a7ca71f23da7f Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:10:55 -0400 Subject: [PATCH 138/488] Rename and rst-i-fy --- Scripts/{README => README.rst} | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) rename Scripts/{README => README.rst} (99%) diff --git a/Scripts/README b/Scripts/README.rst similarity index 99% rename from Scripts/README rename to Scripts/README.rst index befa7e7be..1b26ea68c 100644 --- a/Scripts/README +++ b/Scripts/README.rst @@ -1,6 +1,5 @@ -------- Scripts -------- +======= This directory contains a number of more or less trivial utilities and demo programs. @@ -9,50 +8,50 @@ Comments and contributions are welcome.
--------------------------------------------------------------------- pildriver.py (by Eric S. Raymond) +-------------------------------------------------------------------- A class implementing an image-processing calculator for scripts. Parses lists of commnds (or, called interactively, command-line arguments) into image loads, transformations, and saves. --------------------------------------------------------------------- viewer.py +-------------------------------------------------------------------- A simple image viewer. Can display all file formats handled by PIL. Transparent images are properly handled. --------------------------------------------------------------------- thresholder.py +-------------------------------------------------------------------- A simple utility that demonstrates how a transparent 1-bit overlay can be used to show the current thresholding of an 8-bit image. --------------------------------------------------------------------- enhancer.py +-------------------------------------------------------------------- Illustrates the ImageEnhance module. Drag the sliders to modify the images. This might be very slow on some platforms, depending on the Tk version. --------------------------------------------------------------------- painter.py +-------------------------------------------------------------------- Illustrates how a painting program could be based on PIL and Tk. Press the left mouse button and drag over the image to remove the colour. Some clever tricks have been used to get decent performance when updating the screen; see the sources for details. --------------------------------------------------------------------- player.py +-------------------------------------------------------------------- A simple image sequence player. You can use either a sequence format like FLI/FLC, GIF, or ARG, or give a number of images which are interpreted as frames in a sequence. All frames must have the same size. --------------------------------------------------------------------- gifmaker.py +-------------------------------------------------------------------- Convert a sequence file to a GIF animation. @@ -60,20 +59,20 @@ Note that the GIF encoder provided with this release of PIL writes uncompressed GIF files only, so the resulting animations are rather large compared with these created by other tools. --------------------------------------------------------------------- explode.py +-------------------------------------------------------------------- Split a sequence file into individual frames. --------------------------------------------------------------------- image2py.py +-------------------------------------------------------------------- Convert an image to a Python module containing an IMAGE variable. Note that the module using the module must include JPEG and ZIP decoders, unless the -u option is used. --------------------------------------------------------------------- olesummary.py +-------------------------------------------------------------------- Uses the OleFileIO module to dump the summary information from an OLE structured storage file. This works with most OLE files, including From 562c14dcff0d199cb4e5af9550ce3ee3dd0ebef4 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:11:59 -0400 Subject: [PATCH 139/488] Rename and rst-i-fy --- Sane/{README => README.rst} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename Sane/{README => README.rst} (78%) diff --git a/Sane/README b/Sane/README.rst similarity index 78% rename from Sane/README rename to Sane/README.rst index fa6c8a05f..173934040 100644 --- a/Sane/README +++ b/Sane/README.rst @@ -1,5 +1,5 @@ - Python SANE module V1.1 (30 Sep. 2004) +================================================================================ The SANE module provides an interface to the SANE scanner and frame grabber interface for Linux. This module was contributed by Andrew @@ -9,11 +9,11 @@ word 'SANE' or 'sane' in the subject of your mail, otherwise it might be classified as spam in the future. -To build this module, type (in the Sane directory): +To build this module, type (in the Sane directory):: python setup.py build -In order to install the module type: +In order to install the module type:: python setup.py install From 5ebe01fd2d504219150678a7ce465aa1c242046a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:38:55 -0400 Subject: [PATCH 140/488] Combine *.txt -> README.rst; rst-i-fy --- Tk/README.rst | 291 +++++++++++++++++++++++++++++++++++++++++++++++ Tk/booster.txt | 108 ------------------ Tk/install.txt | 41 ------- Tk/pilbitmap.txt | 149 ------------------------ 4 files changed, 291 insertions(+), 298 deletions(-) create mode 100644 Tk/README.rst delete mode 100644 Tk/booster.txt delete mode 100644 Tk/install.txt delete mode 100644 Tk/pilbitmap.txt diff --git a/Tk/README.rst b/Tk/README.rst new file mode 100644 index 000000000..904762d00 --- /dev/null +++ b/Tk/README.rst @@ -0,0 +1,291 @@ +Using PIL With Tkinter +==================================================================== + +Starting with 1.0 final (release candidate 2 and later, to be +precise), PIL can attach itself to Tkinter in flight. As a result, +you no longer need to rebuild the Tkinter extension to be able to +use PIL. + +However, if you cannot get the this to work on your platform, you +can do it in the old way: + +* Adding Tkinter support + +1. Compile Python's _tkinter.c with the WITH_APPINIT and WITH_PIL + flags set, and link it with tkImaging.c and tkappinit.c. To + do this, copy the former to the Modules directory, and edit + the _tkinter line in Setup (or Setup.in) according to the + instructions in that file. + + NOTE: if you have an old Python version, the tkappinit.c + file is not included by default. If this is the case, you + will have to add the following lines to tkappinit.c, after + the MOREBUTTONS stuff:: + + { + extern void TkImaging_Init(Tcl_Interp* interp); + TkImaging_Init(interp); + } + + This registers a Tcl command called "PyImagingPhoto", which is + use to communicate between PIL and Tk's PhotoImage handler. + + You must also change the _tkinter line in Setup (or Setup.in) + to something like:: + + _tkinter _tkinter.c tkImaging.c tkappinit.c -DWITH_APPINIT + -I/usr/local/include -L/usr/local/lib -ltk8.0 -ltcl8.0 -lX11 + +The Photoimage Booster Patch (for Windows 95/NT) +==================================================================== + + This patch kit boosts performance for 16/24-bit displays. The +first patch is required on Tk 4.2 (where it fixes the problems for +16-bit displays) and later versions, with the exception for Tk 8.0b1 +where Sun added something similar themselves, only to remove it in +8.0b2. By installing both patches, Tk's PhotoImage handling becomes +much faster on both 16-bit and 24-bit displays. The patch has been +tested with Tk 4.2 and 8.0. + + Here's a benchmark, made with a sample program which loads two +512x512 greyscale PGM's, and two 512x512 colour PPM's, and displays +each of them in a separate toplevel windows. Tcl/Tk was compiled +with Visual C 4.0, and run on a P100 under Win95. Image load times +are not included in the timings: + ++----------------------+------------+-------------+----------------+ +| | **8-bit** | **16-bit** | **24-bit** | ++----------------------+------------+-------------+----------------+ +| 1. original 4.2 code | 5.52 s | 8.57 s | 3.79 s | ++----------------------+------------+-------------+----------------+ +| 2. booster patch | 5.49 s | 1.87 s | 1.82 s | ++----------------------+------------+-------------+----------------+ +| speedup | None | 4.6x | 2.1x | ++----------------------+------------+-------------+----------------+ + +Here's the patches: + +1. For portability and speed, the best thing under Windows is to +treat 16-bit displays as if they were 24-bit. The Windows device +drivers take care of the rest. + + ---------------------------------------------------------------- + If you have Tk 4.1 or Tk 8.0b1, you don't have to apply this + patch! It only applies to Tk 4.2, Tk 8.0a[12] and Tk 8.0b2. + ---------------------------------------------------------------- + +In win/tkWinImage.c, change the following line in XCreateImage: + + imagePtr->bits_per_pixel = depth; + +to:: + + /* ==================================================================== */ + /* The tk photo image booster patch -- patch section 1 */ + /* ==================================================================== */ + + if (visual->class == TrueColor) + /* true colour is stored as 3 bytes: (blue, green, red) */ + imagePtr->bits_per_pixel = 24; + else + imagePtr->bits_per_pixel = depth; + + /* ==================================================================== */ + + +2. The DitherInstance implementation is not good. It's especially +bad on highend truecolour displays. IMO, it should be rewritten from +scratch (some other day...). + + Anyway, the following band-aid makes the situation a little bit +better under Windows. This hack trades some marginal quality (no +dithering on 16-bit displays) for a dramatic performance boost. +Requires patch 1, unless you're using Tk 4.1 or Tk 8.0b1. + +In generic/tkImgPhoto.c, add the #ifdef section to the DitherInstance +function:: + + /* ==================================================================== */ + + for (; height > 0; height -= nLines) { + if (nLines > height) { + nLines = height; + } + dstLinePtr = (unsigned char *) imagePtr->data; + yEnd = yStart + nLines; + + /* ==================================================================== */ + /* The tk photo image booster patch -- patch section 2 */ + /* ==================================================================== */ + + #ifdef __WIN32__ + if (colorPtr->visualInfo.class == TrueColor + && instancePtr->gamma == 1.0) { + /* Windows hicolor/truecolor booster */ + for (y = yStart; y < yEnd; ++y) { + destBytePtr = dstLinePtr; + srcPtr = srcLinePtr; + for (x = xStart; x < xEnd; ++x) { + destBytePtr[0] = srcPtr[2]; + destBytePtr[1] = srcPtr[1]; + destBytePtr[2] = srcPtr[0]; + destBytePtr += 3; srcPtr += 3; + } + srcLinePtr += lineLength; + dstLinePtr += bytesPerLine; + } + } else + #endif + + /* ==================================================================== */ + + for (y = yStart; y < yEnd; ++y) { + srcPtr = srcLinePtr; + errPtr = errLinePtr; + destBytePtr = dstLinePtr; + +last updated: 97-07-03/fl + + +The PIL Bitmap Booster Patch +==================================================================== + +The pilbitmap booster patch greatly improves performance of the +ImageTk.BitmapImage constructor. Unfortunately, the design of Tk +doesn't allow us to do this from the tkImaging interface module, so +you have to patch the Tk sources. + +Once installed, the ImageTk module will automatically detect this +patch. + +(Note: this patch has been tested with Tk 8.0 on Win32 only, but it +should work just fine on other platforms as well). + +1. To the beginning of TkGetBitmapData (in generic/tkImgBmap.c), add + the following stuff:: + + /* ==================================================================== */ + + int width, height, numBytes, hotX, hotY; + char *p, *end, *expandedFileName; + ParseInfo pi; + char *data = NULL; + Tcl_DString buffer; + + /* ==================================================================== */ + /* The pilbitmap booster patch -- patch section */ + /* ==================================================================== */ + + char *PILGetBitmapData(); + + if (string) { + /* Is this a PIL bitmap reference? */ + data = PILGetBitmapData(string, widthPtr, heightPtr, hotXPtr, hotYPtr); + if (data) + return data; + } + + /* ==================================================================== */ + + pi.string = string; + if (string == NULL) { + if (Tcl_IsSafe(interp)) { + +2. Append the following to the same file (you may wish to include +Imaging.h instead of copying the struct declaration...):: + + /* ==================================================================== */ + /* The pilbitmap booster patch -- code section */ + /* ==================================================================== */ + + /* Imaging declaration boldly copied from Imaging.h (!) */ + + typedef struct ImagingInstance *Imaging; /* a.k.a. ImagingImage :-) */ + + typedef unsigned char UINT8; + typedef int INT32; + + struct ImagingInstance { + + /* Format */ + char mode[4+1]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK") */ + int type; /* Always 0 in this version */ + int depth; /* Always 8 in this version */ + int bands; /* Number of bands (1, 3, or 4) */ + int xsize; /* Image dimension. */ + int ysize; + + /* Colour palette (for "P" images only) */ + void* palette; + + /* Data pointers */ + UINT8 **image8; /* Set for 8-bit image (pixelsize=1). */ + INT32 **image32; /* Set for 32-bit image (pixelsize=4). */ + + /* Internals */ + char **image; /* Actual raster data. */ + char *block; /* Set if data is allocated in a single block. */ + + int pixelsize; /* Size of a pixel, in bytes (1 or 4) */ + int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ + + /* Virtual methods */ + void (*im_delete)(Imaging *); + + }; + + /* The pilbitmap booster patch allows you to pass PIL images to the + Tk bitmap decoder. Passing images this way is much more efficient + than using the "tobitmap" method. */ + + char * + PILGetBitmapData(string, widthPtr, heightPtr, hotXPtr, hotYPtr) + char *string; + int *widthPtr, *heightPtr; + int *hotXPtr, *hotYPtr; + { + char* data; + char* p; + int y; + Imaging im; + + if (strncmp(string, "PIL:", 4) != 0) + return NULL; + + im = (Imaging) atol(string + 4); + + if (strcmp(im->mode, "1") != 0 && strcmp(im->mode, "L") != 0) + return NULL; + + data = p = (char *) ckalloc((unsigned) ((im->xsize+7)/8) * im->ysize); + + for (y = 0; y < im->ysize; y++) { + char* in = im->image8[y]; + int i, m, b; + b = 0; m = 1; + for (i = 0; i < im->xsize; i++) { + if (in[i] != 0) + b |= m; + m <<= 1; + if (m == 256){ + *p++ = b; + b = 0; m = 1; + } + } + if (m != 1) + *p++ = b; + } + + *widthPtr = im->xsize; + *heightPtr = im->ysize; + *hotXPtr = -1; + *hotYPtr = -1; + + return data; + } + + /* ==================================================================== */ + +3. Recompile Tk and relink the _tkinter module (where necessary). + +Last updated: 97-05-17/fl diff --git a/Tk/booster.txt b/Tk/booster.txt deleted file mode 100644 index 2d787983b..000000000 --- a/Tk/booster.txt +++ /dev/null @@ -1,108 +0,0 @@ -==================================================================== -The Photoimage Booster Patch (for Windows 95/NT) -==================================================================== - - This patch kit boosts performance for 16/24-bit displays. The -first patch is required on Tk 4.2 (where it fixes the problems for -16-bit displays) and later versions, with the exception for Tk 8.0b1 -where Sun added something similar themselves, only to remove it in -8.0b2. By installing both patches, Tk's PhotoImage handling becomes -much faster on both 16-bit and 24-bit displays. The patch has been -tested with Tk 4.2 and 8.0. - - Here's a benchmark, made with a sample program which loads two -512x512 greyscale PGM's, and two 512x512 colour PPM's, and displays -each of them in a separate toplevel windows. Tcl/Tk was compiled -with Visual C 4.0, and run on a P100 under Win95. Image load times -are not included in the timings: - - 8-bit 16-bit 24-bit --------------------------------------------------------------------- -1. original 4.2 code 5.52 s 8.57 s 3.79 s -2. booster patch 5.49 s 1.87 s 1.82 s - - speedup None 4.6x 2.1x - -==================================================================== - -Here's the patches: - -1. For portability and speed, the best thing under Windows is to -treat 16-bit displays as if they were 24-bit. The Windows device -drivers take care of the rest. - - ---------------------------------------------------------------- - If you have Tk 4.1 or Tk 8.0b1, you don't have to apply this - patch! It only applies to Tk 4.2, Tk 8.0a[12] and Tk 8.0b2. - ---------------------------------------------------------------- - -In win/tkWinImage.c, change the following line in XCreateImage: - - imagePtr->bits_per_pixel = depth; - -to - -/* ==================================================================== */ -/* The tk photo image booster patch -- patch section 1 */ -/* ==================================================================== */ - - if (visual->class == TrueColor) - /* true colour is stored as 3 bytes: (blue, green, red) */ - imagePtr->bits_per_pixel = 24; - else - imagePtr->bits_per_pixel = depth; - -/* ==================================================================== */ - - -2. The DitherInstance implementation is not good. It's especially -bad on highend truecolour displays. IMO, it should be rewritten from -scratch (some other day...). - - Anyway, the following band-aid makes the situation a little bit -better under Windows. This hack trades some marginal quality (no -dithering on 16-bit displays) for a dramatic performance boost. -Requires patch 1, unless you're using Tk 4.1 or Tk 8.0b1. - -In generic/tkImgPhoto.c, add the #ifdef section to the DitherInstance -function: - - for (; height > 0; height -= nLines) { - if (nLines > height) { - nLines = height; - } - dstLinePtr = (unsigned char *) imagePtr->data; - yEnd = yStart + nLines; - -/* ==================================================================== */ -/* The tk photo image booster patch -- patch section 2 */ -/* ==================================================================== */ - -#ifdef __WIN32__ - if (colorPtr->visualInfo.class == TrueColor - && instancePtr->gamma == 1.0) { - /* Windows hicolor/truecolor booster */ - for (y = yStart; y < yEnd; ++y) { - destBytePtr = dstLinePtr; - srcPtr = srcLinePtr; - for (x = xStart; x < xEnd; ++x) { - destBytePtr[0] = srcPtr[2]; - destBytePtr[1] = srcPtr[1]; - destBytePtr[2] = srcPtr[0]; - destBytePtr += 3; srcPtr += 3; - } - srcLinePtr += lineLength; - dstLinePtr += bytesPerLine; - } - } else -#endif - -/* ==================================================================== */ - - for (y = yStart; y < yEnd; ++y) { - srcPtr = srcLinePtr; - errPtr = errLinePtr; - destBytePtr = dstLinePtr; - -==================================================================== -last updated: 97-07-03/fl diff --git a/Tk/install.txt b/Tk/install.txt deleted file mode 100644 index 0e2ade06d..000000000 --- a/Tk/install.txt +++ /dev/null @@ -1,41 +0,0 @@ -==================================================================== -Using PIL With Tkinter -==================================================================== - -Starting with 1.0 final (release candidate 2 and later, to be -precise), PIL can attach itself to Tkinter in flight. As a result, -you no longer need to rebuild the Tkinter extension to be able to -use PIL. - -However, if you cannot get the this to work on your platform, you -can do it in the old way: - -* Adding Tkinter support - -1. Compile Python's _tkinter.c with the WITH_APPINIT and WITH_PIL - flags set, and link it with tkImaging.c and tkappinit.c. To - do this, copy the former to the Modules directory, and edit - the _tkinter line in Setup (or Setup.in) according to the - instructions in that file. - - NOTE: if you have an old Python version, the tkappinit.c - file is not included by default. If this is the case, you - will have to add the following lines to tkappinit.c, after - the MOREBUTTONS stuff: - - { - extern void TkImaging_Init(Tcl_Interp* interp); - TkImaging_Init(interp); - } - - This registers a Tcl command called "PyImagingPhoto", which is - use to communicate between PIL and Tk's PhotoImage handler. - - You must also change the _tkinter line in Setup (or Setup.in) - to something like: - - _tkinter _tkinter.c tkImaging.c tkappinit.c -DWITH_APPINIT - -I/usr/local/include -L/usr/local/lib -ltk8.0 -ltcl8.0 -lX11 - - - diff --git a/Tk/pilbitmap.txt b/Tk/pilbitmap.txt deleted file mode 100644 index e7c46c65f..000000000 --- a/Tk/pilbitmap.txt +++ /dev/null @@ -1,149 +0,0 @@ -==================================================================== -The PIL Bitmap Booster Patch -==================================================================== - -The pilbitmap booster patch greatly improves performance of the -ImageTk.BitmapImage constructor. Unfortunately, the design of Tk -doesn't allow us to do this from the tkImaging interface module, so -you have to patch the Tk sources. - -Once installed, the ImageTk module will automatically detect this -patch. - -(Note: this patch has been tested with Tk 8.0 on Win32 only, but it -should work just fine on other platforms as well). - -1. To the beginning of TkGetBitmapData (in generic/tkImgBmap.c), add - the following stuff: - ------------------------------------------------------------------------- - int width, height, numBytes, hotX, hotY; - char *p, *end, *expandedFileName; - ParseInfo pi; - char *data = NULL; - Tcl_DString buffer; - -/* ==================================================================== */ -/* The pilbitmap booster patch -- patch section */ -/* ==================================================================== */ - - char *PILGetBitmapData(); - - if (string) { - /* Is this a PIL bitmap reference? */ - data = PILGetBitmapData(string, widthPtr, heightPtr, hotXPtr, hotYPtr); - if (data) - return data; - } - -/* ==================================================================== */ - - pi.string = string; - if (string == NULL) { - if (Tcl_IsSafe(interp)) { ------------------------------------------------------------------------- - - -2. Append the following to the same file (you may wish to include -Imaging.h instead of copying the struct declaration...) - ------------------------------------------------------------------------- - -/* ==================================================================== */ -/* The pilbitmap booster patch -- code section */ -/* ==================================================================== */ - -/* Imaging declaration boldly copied from Imaging.h (!) */ - -typedef struct ImagingInstance *Imaging; /* a.k.a. ImagingImage :-) */ - -typedef unsigned char UINT8; -typedef int INT32; - -struct ImagingInstance { - - /* Format */ - char mode[4+1]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK") */ - int type; /* Always 0 in this version */ - int depth; /* Always 8 in this version */ - int bands; /* Number of bands (1, 3, or 4) */ - int xsize; /* Image dimension. */ - int ysize; - - /* Colour palette (for "P" images only) */ - void* palette; - - /* Data pointers */ - UINT8 **image8; /* Set for 8-bit image (pixelsize=1). */ - INT32 **image32; /* Set for 32-bit image (pixelsize=4). */ - - /* Internals */ - char **image; /* Actual raster data. */ - char *block; /* Set if data is allocated in a single block. */ - - int pixelsize; /* Size of a pixel, in bytes (1 or 4) */ - int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ - - /* Virtual methods */ - void (*im_delete)(Imaging *); - -}; - -/* The pilbitmap booster patch allows you to pass PIL images to the - Tk bitmap decoder. Passing images this way is much more efficient - than using the "tobitmap" method. */ - -char * -PILGetBitmapData(string, widthPtr, heightPtr, hotXPtr, hotYPtr) - char *string; - int *widthPtr, *heightPtr; - int *hotXPtr, *hotYPtr; -{ - char* data; - char* p; - int y; - Imaging im; - - if (strncmp(string, "PIL:", 4) != 0) - return NULL; - - im = (Imaging) atol(string + 4); - - if (strcmp(im->mode, "1") != 0 && strcmp(im->mode, "L") != 0) - return NULL; - - data = p = (char *) ckalloc((unsigned) ((im->xsize+7)/8) * im->ysize); - - for (y = 0; y < im->ysize; y++) { - char* in = im->image8[y]; - int i, m, b; - b = 0; m = 1; - for (i = 0; i < im->xsize; i++) { - if (in[i] != 0) - b |= m; - m <<= 1; - if (m == 256){ - *p++ = b; - b = 0; m = 1; - } - } - if (m != 1) - *p++ = b; - } - - *widthPtr = im->xsize; - *heightPtr = im->ysize; - *hotXPtr = -1; - *hotYPtr = -1; - - return data; -} - -/* ==================================================================== */ - ------------------------------------------------------------------------- - -3. Recompile Tk and relink the _tkinter module (where necessary). - -==================================================================== -Last updated: 97-05-17/fl From 409ada85056decf8471835cf9f103b0361202425 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:44:53 -0400 Subject: [PATCH 141/488] Clean up --- Tk/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tk/README.rst b/Tk/README.rst index 904762d00..b379add17 100644 --- a/Tk/README.rst +++ b/Tk/README.rst @@ -9,7 +9,7 @@ use PIL. However, if you cannot get the this to work on your platform, you can do it in the old way: -* Adding Tkinter support +Adding Tkinter support 1. Compile Python's _tkinter.c with the WITH_APPINIT and WITH_PIL flags set, and link it with tkImaging.c and tkappinit.c. To @@ -74,7 +74,7 @@ drivers take care of the rest. patch! It only applies to Tk 4.2, Tk 8.0a[12] and Tk 8.0b2. ---------------------------------------------------------------- -In win/tkWinImage.c, change the following line in XCreateImage: +In win/tkWinImage.c, change the following line in XCreateImage:: imagePtr->bits_per_pixel = depth; From d1130b4d09da28e6fbac321323c4540f1a2ce44b Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:48:00 -0400 Subject: [PATCH 142/488] Clean up --- Tk/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tk/README.rst b/Tk/README.rst index b379add17..d49a02843 100644 --- a/Tk/README.rst +++ b/Tk/README.rst @@ -69,10 +69,10 @@ Here's the patches: treat 16-bit displays as if they were 24-bit. The Windows device drivers take care of the rest. - ---------------------------------------------------------------- +.. Note:: + If you have Tk 4.1 or Tk 8.0b1, you don't have to apply this patch! It only applies to Tk 4.2, Tk 8.0a[12] and Tk 8.0b2. - ---------------------------------------------------------------- In win/tkWinImage.c, change the following line in XCreateImage:: From 4b2bc94b198209efcf08e0ce9ef60a6883f344ac Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:48:49 -0400 Subject: [PATCH 143/488] Clean up --- Tk/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tk/README.rst b/Tk/README.rst index d49a02843..1fff4d314 100644 --- a/Tk/README.rst +++ b/Tk/README.rst @@ -39,7 +39,7 @@ Adding Tkinter support The Photoimage Booster Patch (for Windows 95/NT) ==================================================================== - This patch kit boosts performance for 16/24-bit displays. The +This patch kit boosts performance for 16/24-bit displays. The first patch is required on Tk 4.2 (where it fixes the problems for 16-bit displays) and later versions, with the exception for Tk 8.0b1 where Sun added something similar themselves, only to remove it in @@ -47,7 +47,7 @@ where Sun added something similar themselves, only to remove it in much faster on both 16-bit and 24-bit displays. The patch has been tested with Tk 4.2 and 8.0. - Here's a benchmark, made with a sample program which loads two +Here's a benchmark, made with a sample program which loads two 512x512 greyscale PGM's, and two 512x512 colour PPM's, and displays each of them in a separate toplevel windows. Tcl/Tk was compiled with Visual C 4.0, and run on a P100 under Win95. Image load times @@ -97,7 +97,7 @@ to:: bad on highend truecolour displays. IMO, it should be rewritten from scratch (some other day...). - Anyway, the following band-aid makes the situation a little bit +Anyway, the following band-aid makes the situation a little bit better under Windows. This hack trades some marginal quality (no dithering on 16-bit displays) for a dramatic performance boost. Requires patch 1, unless you're using Tk 4.1 or Tk 8.0b1. From 0cbf318004ea7fc462266739a6a2a82da106a73b Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 22 Jun 2014 20:49:42 -0400 Subject: [PATCH 144/488] Update --- Tk/README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tk/README.rst b/Tk/README.rst index 1fff4d314..bb5eb9c3c 100644 --- a/Tk/README.rst +++ b/Tk/README.rst @@ -10,6 +10,7 @@ However, if you cannot get the this to work on your platform, you can do it in the old way: Adding Tkinter support +---------------------- 1. Compile Python's _tkinter.c with the WITH_APPINIT and WITH_PIL flags set, and link it with tkImaging.c and tkappinit.c. To @@ -74,7 +75,7 @@ drivers take care of the rest. If you have Tk 4.1 or Tk 8.0b1, you don't have to apply this patch! It only applies to Tk 4.2, Tk 8.0a[12] and Tk 8.0b2. -In win/tkWinImage.c, change the following line in XCreateImage:: +In ``win/tkWinImage.c``, change the following line in ``XCreateImage``:: imagePtr->bits_per_pixel = depth; From a600f84029383968a28e6d29bf308204cdb4c1d9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 23 Jun 2014 09:04:23 +0300 Subject: [PATCH 145/488] Move images from Images/ to Tests/images/ --- {Images => Tests/images}/courB08.bdf | 0 {Images => Tests/images}/courB08.pbm | Bin {Images => Tests/images}/courB08.pil | Bin {Images => Tests/images}/flower.webp | Bin {Images => Tests/images}/flower2.webp | Bin {Images => Tests/images}/lena.fli | Bin {Images => Tests/images}/lena.gif | Bin {Images => Tests/images}/lena.ico | Bin {Images => Tests/images}/lena.jpg | Bin {Images => Tests/images}/lena.png | Bin {Images => Tests/images}/lena.ppm | Bin {Images => Tests/images}/lena.psd | Bin {Images => Tests/images}/lena.tar | Bin {Images => Tests/images}/lena.webp | Bin {Images => Tests/images}/lena.xpm | 0 {Images => Tests/images}/pillow.icns | Bin {Images => Tests/images}/pillow.ico | Bin {Images => Tests/images}/transparent.png | Bin {Images => Tests/images}/transparent.webp | Bin 19 files changed, 0 insertions(+), 0 deletions(-) rename {Images => Tests/images}/courB08.bdf (100%) rename {Images => Tests/images}/courB08.pbm (100%) rename {Images => Tests/images}/courB08.pil (100%) rename {Images => Tests/images}/flower.webp (100%) rename {Images => Tests/images}/flower2.webp (100%) rename {Images => Tests/images}/lena.fli (100%) rename {Images => Tests/images}/lena.gif (100%) rename {Images => Tests/images}/lena.ico (100%) rename {Images => Tests/images}/lena.jpg (100%) rename {Images => Tests/images}/lena.png (100%) rename {Images => Tests/images}/lena.ppm (100%) rename {Images => Tests/images}/lena.psd (100%) rename {Images => Tests/images}/lena.tar (100%) rename {Images => Tests/images}/lena.webp (100%) rename {Images => Tests/images}/lena.xpm (100%) rename {Images => Tests/images}/pillow.icns (100%) rename {Images => Tests/images}/pillow.ico (100%) rename {Images => Tests/images}/transparent.png (100%) rename {Images => Tests/images}/transparent.webp (100%) diff --git a/Images/courB08.bdf b/Tests/images/courB08.bdf similarity index 100% rename from Images/courB08.bdf rename to Tests/images/courB08.bdf diff --git a/Images/courB08.pbm b/Tests/images/courB08.pbm similarity index 100% rename from Images/courB08.pbm rename to Tests/images/courB08.pbm diff --git a/Images/courB08.pil b/Tests/images/courB08.pil similarity index 100% rename from Images/courB08.pil rename to Tests/images/courB08.pil diff --git a/Images/flower.webp b/Tests/images/flower.webp similarity index 100% rename from Images/flower.webp rename to Tests/images/flower.webp diff --git a/Images/flower2.webp b/Tests/images/flower2.webp similarity index 100% rename from Images/flower2.webp rename to Tests/images/flower2.webp diff --git a/Images/lena.fli b/Tests/images/lena.fli similarity index 100% rename from Images/lena.fli rename to Tests/images/lena.fli diff --git a/Images/lena.gif b/Tests/images/lena.gif similarity index 100% rename from Images/lena.gif rename to Tests/images/lena.gif diff --git a/Images/lena.ico b/Tests/images/lena.ico similarity index 100% rename from Images/lena.ico rename to Tests/images/lena.ico diff --git a/Images/lena.jpg b/Tests/images/lena.jpg similarity index 100% rename from Images/lena.jpg rename to Tests/images/lena.jpg diff --git a/Images/lena.png b/Tests/images/lena.png similarity index 100% rename from Images/lena.png rename to Tests/images/lena.png diff --git a/Images/lena.ppm b/Tests/images/lena.ppm similarity index 100% rename from Images/lena.ppm rename to Tests/images/lena.ppm diff --git a/Images/lena.psd b/Tests/images/lena.psd similarity index 100% rename from Images/lena.psd rename to Tests/images/lena.psd diff --git a/Images/lena.tar b/Tests/images/lena.tar similarity index 100% rename from Images/lena.tar rename to Tests/images/lena.tar diff --git a/Images/lena.webp b/Tests/images/lena.webp similarity index 100% rename from Images/lena.webp rename to Tests/images/lena.webp diff --git a/Images/lena.xpm b/Tests/images/lena.xpm similarity index 100% rename from Images/lena.xpm rename to Tests/images/lena.xpm diff --git a/Images/pillow.icns b/Tests/images/pillow.icns similarity index 100% rename from Images/pillow.icns rename to Tests/images/pillow.icns diff --git a/Images/pillow.ico b/Tests/images/pillow.ico similarity index 100% rename from Images/pillow.ico rename to Tests/images/pillow.ico diff --git a/Images/transparent.png b/Tests/images/transparent.png similarity index 100% rename from Images/transparent.png rename to Tests/images/transparent.png diff --git a/Images/transparent.webp b/Tests/images/transparent.webp similarity index 100% rename from Images/transparent.webp rename to Tests/images/transparent.webp From 78003ca473425bdb7df8d3ea555c70875d9195a0 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 23 Jun 2014 09:19:29 +0300 Subject: [PATCH 146/488] Update paths to Tests/images/ instead of Images/ --- PIL/ImageFont.py | 2 +- Tests/helper.py | 2 +- Tests/test_file_fli.py | 2 +- Tests/test_file_gif.py | 6 +++--- Tests/test_file_icns.py | 2 +- Tests/test_file_ico.py | 2 +- Tests/test_file_jpeg.py | 4 ++-- Tests/test_file_png.py | 6 +++--- Tests/test_file_ppm.py | 2 +- Tests/test_file_psd.py | 2 +- Tests/test_file_tar.py | 2 +- Tests/test_file_webp.py | 4 ++-- Tests/test_file_webp_alpha.py | 4 ++-- Tests/test_file_webp_metadata.py | 4 ++-- Tests/test_file_xpm.py | 2 +- Tests/test_font_bdf.py | 2 +- Tests/test_image_convert.py | 2 +- Tests/test_image_draft.py | 2 +- Tests/test_image_load.py | 4 ++-- Tests/test_imageops_usm.py | 2 +- Tests/test_locale.py | 2 +- Tests/test_pickle.py | 4 ++-- Tests/tester.py | 2 +- Tests/threaded_save.py | 2 +- selftest.py | 10 +++++----- 25 files changed, 39 insertions(+), 39 deletions(-) diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py index 9366937dd..18d09b871 100644 --- a/PIL/ImageFont.py +++ b/PIL/ImageFont.py @@ -396,7 +396,7 @@ w7IkEbzhVQAAAABJRU5ErkJggg== if __name__ == "__main__": # create font data chunk for embedding import base64, os, sys - font = "../Images/courB08" + font = "../Tests/images/courB08" print(" f._load_pilfont_data(") print(" # %s" % os.path.basename(font)) print(" BytesIO(base64.decodestring(b'''") diff --git a/Tests/helper.py b/Tests/helper.py index c203e67c2..aacbfc009 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -233,7 +233,7 @@ def lena(mode="RGB", cache={}): # im = cache.get(mode) if im is None: if mode == "RGB": - im = Image.open("Images/lena.ppm") + im = Image.open("Tests/images/lena.ppm") elif mode == "F": im = lena("L").convert(mode) elif mode[:4] == "I;16": diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 787365c14..a98a80b78 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image # sample ppm stream -file = "Images/lena.fli" +file = "Tests/images/lena.fli" data = open(file, "rb").read() diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 3aefbe9b3..01ac1d95c 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -5,7 +5,7 @@ from PIL import Image codecs = dir(Image.core) # sample gif stream -file = "Images/lena.gif" +file = "Tests/images/lena.gif" with open(file, "rb") as f: data = f.read() @@ -45,7 +45,7 @@ class TestFileGif(PillowTestCase): def test_roundtrip2(self): # see https://github.com/python-pillow/Pillow/issues/403 out = self.tempfile('temp.gif') - im = Image.open('Images/lena.gif') + im = Image.open('Tests/images/lena.gif') im2 = im.copy() im2.save(out) reread = Image.open(out) @@ -55,7 +55,7 @@ class TestFileGif(PillowTestCase): def test_palette_handling(self): # see https://github.com/python-pillow/Pillow/issues/513 - im = Image.open('Images/lena.gif') + im = Image.open('Tests/images/lena.gif') im = im.convert('RGB') im = im.resize((100, 100), Image.ANTIALIAS) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index c307ec844..f19eb16b7 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image # sample icon file -file = "Images/pillow.icns" +file = "Tests/images/pillow.icns" data = open(file, "rb").read() enable_jpeg2k = hasattr(Image.core, 'jp2klib_version') diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 5bad94a53..165d10225 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image # sample ppm stream -file = "Images/lena.ico" +file = "Tests/images/lena.ico" data = open(file, "rb").read() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index c21910962..158299c36 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -8,7 +8,7 @@ from PIL import ImageFile codecs = dir(Image.core) -test_file = "Images/lena.jpg" +test_file = "Tests/images/lena.jpg" class TestFileJpeg(PillowTestCase): @@ -215,7 +215,7 @@ class TestFileJpeg(PillowTestCase): self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh') def test_quality_keep(self): - im = Image.open("Images/lena.jpg") + im = Image.open("Tests/images/lena.jpg") f = self.tempfile('temp.jpg') im.save(f, quality='keep') diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 6da61be6b..145eff327 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -10,7 +10,7 @@ codecs = dir(Image.core) # sample png stream -file = "Images/lena.png" +file = "Tests/images/lena.png" data = open(file, "rb").read() # stuff to create inline PNG images @@ -204,10 +204,10 @@ class TestFilePng(PillowTestCase): def test_load_verify(self): # Check open/load/verify exception (@PIL150) - im = Image.open("Images/lena.png") + im = Image.open("Tests/images/lena.png") im.verify() - im = Image.open("Images/lena.png") + im = Image.open("Tests/images/lena.png") im.load() self.assertRaises(RuntimeError, lambda: im.verify()) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 9d5dd806a..d9e4e0674 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image # sample ppm stream -file = "Images/lena.ppm" +file = "Tests/images/lena.ppm" data = open(file, "rb").read() diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index e7f86fd98..007646901 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image # sample ppm stream -file = "Images/lena.psd" +file = "Tests/images/lena.psd" data = open(file, "rb").read() diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 36a625add..7e36f35fc 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -5,7 +5,7 @@ from PIL import Image, TarIO codecs = dir(Image.core) # sample ppm stream -tarfile = "Images/lena.tar" +tarfile = "Tests/images/lena.tar" class TestFileTar(PillowTestCase): diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index ea1c2e19f..1eeea57d3 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -23,7 +23,7 @@ class TestFileWebp(PillowTestCase): def test_read_rgb(self): - file_path = "Images/lena.webp" + file_path = "Tests/images/lena.webp" image = Image.open(file_path) self.assertEqual(image.mode, "RGB") @@ -33,7 +33,7 @@ class TestFileWebp(PillowTestCase): image.getdata() # generated with: - # dwebp -ppm ../../Images/lena.webp -o lena_webp_bits.ppm + # dwebp -ppm ../../Tests/images/lena.webp -o lena_webp_bits.ppm target = Image.open('Tests/images/lena_webp_bits.ppm') self.assert_image_similar(image, target, 20.0) diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index c9787c60e..0df3143bb 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -22,7 +22,7 @@ class TestFileWebpAlpha(PillowTestCase): def test_read_rgba(self): # Generated with `cwebp transparent.png -o transparent.webp` - file_path = "Images/transparent.webp" + file_path = "Tests/images/transparent.webp" image = Image.open(file_path) self.assertEqual(image.mode, "RGBA") @@ -33,7 +33,7 @@ class TestFileWebpAlpha(PillowTestCase): image.tobytes() - target = Image.open('Images/transparent.png') + target = Image.open('Tests/images/transparent.png') self.assert_image_similar(image, target, 20.0) def test_write_lossless_rgb(self): diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 93021ac07..2470f4c49 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -15,7 +15,7 @@ class TestFileWebpMetadata(PillowTestCase): def test_read_exif_metadata(self): - file_path = "Images/flower.webp" + file_path = "Tests/images/flower.webp" image = Image.open(file_path) self.assertEqual(image.format, "WEBP") @@ -54,7 +54,7 @@ class TestFileWebpMetadata(PillowTestCase): def test_read_icc_profile(self): - file_path = "Images/flower2.webp" + file_path = "Tests/images/flower2.webp" image = Image.open(file_path) self.assertEqual(image.format, "WEBP") diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 0c765da8e..e6e750298 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image # sample ppm stream -file = "Images/lena.xpm" +file = "Tests/images/lena.xpm" data = open(file, "rb").read() diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 9a8f19d03..ce5a371e0 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -2,7 +2,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import FontFile, BdfFontFile -filename = "Images/courB08.bdf" +filename = "Tests/images/courB08.bdf" class TestFontBdf(PillowTestCase): diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 946dddc11..1415bae3a 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -36,7 +36,7 @@ class TestImageConvert(PillowTestCase): self.assertEqual(orig, converted) def test_8bit(self): - im = Image.open('Images/lena.jpg') + im = Image.open('Tests/images/lena.jpg') self._test_float_conversion(im.convert('L')) def test_16bit(self): diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index e970c9794..252e60376 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase, tearDownModule, fromstring, tostrin from PIL import Image codecs = dir(Image.core) -filename = "Images/lena.jpg" +filename = "Tests/images/lena.jpg" data = tostring(Image.open(filename).resize((512, 512)), "JPEG") diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index cf23a0101..14cb76fdb 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -16,14 +16,14 @@ class TestImageLoad(PillowTestCase): self.assertEqual(pix[0, 0], (223, 162, 133)) def test_close(self): - im = Image.open("Images/lena.gif") + im = Image.open("Tests/images/lena.gif") im.close() self.assertRaises(ValueError, lambda: im.load()) self.assertRaises(ValueError, lambda: im.getpixel((0, 0))) def test_contextmanager(self): fn = None - with Image.open("Images/lena.gif") as im: + with Image.open("Tests/images/lena.gif") as im: fn = im.fp.fileno() os.fstat(fn) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 2f81eebce..486b201ab 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -4,7 +4,7 @@ from PIL import Image from PIL import ImageOps from PIL import ImageFilter -im = Image.open("Images/lena.ppm") +im = Image.open("Tests/images/lena.ppm") class TestImageOpsUsm(PillowTestCase): diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 7ef63634b..0465fb207 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -19,7 +19,7 @@ import locale # one of string.whitespace is not freely convertable into ascii. -path = "Images/lena.jpg" +path = "Tests/images/lena.jpg" class TestLocale(PillowTestCase): diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 5f3ae6f51..304baf964 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -6,7 +6,7 @@ from PIL import Image class TestPickle(PillowTestCase): def helper_pickle_file(self, pickle, protocol=0): - im = Image.open('Images/lena.jpg') + im = Image.open('Tests/images/lena.jpg') filename = self.tempfile('temp.pkl') # Act @@ -19,7 +19,7 @@ class TestPickle(PillowTestCase): self.assertEqual(im, loaded_im) def helper_pickle_string( - self, pickle, protocol=0, file='Images/lena.jpg'): + self, pickle, protocol=0, file='Tests/images/lena.jpg'): im = Image.open(file) # Act diff --git a/Tests/tester.py b/Tests/tester.py index 4476aced1..866009251 100644 --- a/Tests/tester.py +++ b/Tests/tester.py @@ -215,7 +215,7 @@ def lena(mode="RGB", cache={}): im = cache.get(mode) if im is None: if mode == "RGB": - im = Image.open("Images/lena.ppm") + im = Image.open("Tests/images/lena.ppm") elif mode == "F": im = lena("L").convert(mode) elif mode[:4] == "I;16": diff --git a/Tests/threaded_save.py b/Tests/threaded_save.py index 471e24815..12fcff2cf 100644 --- a/Tests/threaded_save.py +++ b/Tests/threaded_save.py @@ -9,7 +9,7 @@ try: except: format = "PNG" -im = Image.open("Images/lena.ppm") +im = Image.open("Tests/images/lena.ppm") im.load() queue = queue.Queue() diff --git a/selftest.py b/selftest.py index 248cb3937..29af34ad2 100644 --- a/selftest.py +++ b/selftest.py @@ -49,13 +49,13 @@ def testimage(): Or open existing files: - >>> im = Image.open(os.path.join(ROOT, "Images/lena.gif")) + >>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.gif")) >>> _info(im) ('GIF', 'P', (128, 128)) - >>> _info(Image.open(os.path.join(ROOT, "Images/lena.ppm"))) + >>> _info(Image.open(os.path.join(ROOT, "Tests/images/lena.ppm"))) ('PPM', 'RGB', (128, 128)) >>> try: - ... _info(Image.open(os.path.join(ROOT, "Images/lena.jpg"))) + ... _info(Image.open(os.path.join(ROOT, "Tests/images/lena.jpg"))) ... except IOError as v: ... print(v) ('JPEG', 'RGB', (128, 128)) @@ -63,7 +63,7 @@ def testimage(): PIL doesn't actually load the image data until it's needed, or you call the "load" method: - >>> im = Image.open(os.path.join(ROOT, "Images/lena.ppm")) + >>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.ppm")) >>> print(im.im) # internal image attribute None >>> a = im.load() @@ -73,7 +73,7 @@ def testimage(): You can apply many different operations on images. Most operations return a new image: - >>> im = Image.open(os.path.join(ROOT, "Images/lena.ppm")) + >>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.ppm")) >>> _info(im.convert("L")) (None, 'L', (128, 128)) >>> _info(im.copy()) From 46abe78b775e445974cfb9603640581b9fd9852c Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 23 Jun 2014 10:53:08 +0300 Subject: [PATCH 147/488] Use a custom subclass of RuntimeWarning for DecompressionBombWarning --- PIL/Image.py | 5 ++++- Tests/test_decompression_bomb.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index ce78bfa1b..b0b90a20b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -31,6 +31,9 @@ from PIL import VERSION, PILLOW_VERSION, _plugins import warnings +class DecompressionBombWarning(RuntimeWarning): + pass + class _imaging_not_installed: # module placeholder def __getattr__(self, id): @@ -2187,7 +2190,7 @@ def _decompression_bomb_check(size): "Image size (%d pixels) exceeds limit of %d pixels, " "could be decompression bomb DOS attack." % (pixels, MAX_IMAGE_PIXELS), - RuntimeWarning) + DecompressionBombWarning) def open(fp, mode="r"): diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 1b3873c1b..53c8bb5e6 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -31,7 +31,7 @@ def test_warning(): # Act / Assert assert_warning( - RuntimeWarning, + DecompressionBombWarning, lambda: Image.open(test_file)) # End of file From 7b3e242f09b128b5cf181cde69d4267ebf8761cb Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 23 Jun 2014 11:12:41 +0300 Subject: [PATCH 148/488] Convert test_decompression_bomb.py to use unittest module --- Tests/test_decompression_bomb.py | 49 +++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 53c8bb5e6..1f85b1b9a 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,37 +1,40 @@ -from tester import * +from helper import unittest, PillowTestCase, tearDownModule from PIL import Image test_file = "Images/lena.ppm" -def test_no_warning_small_file(): - # Implicit assert: no warning. - # A warning would cause a failure. - Image.open(test_file) +class TestDecompressionBomb(PillowTestCase): + def test_no_warning_small_file(self): + # Implicit assert: no warning. + # A warning would cause a failure. + Image.open(test_file) -def test_no_warning_no_limit(): - # Arrange - # Turn limit off - Image.MAX_IMAGE_PIXELS = None - assert_equal(Image.MAX_IMAGE_PIXELS, None) + def test_no_warning_no_limit(self): + # Arrange + # Turn limit off + Image.MAX_IMAGE_PIXELS = None + self.assertEqual(Image.MAX_IMAGE_PIXELS, None) - # Act / Assert - # Implicit assert: no warning. - # A warning would cause a failure. - Image.open(test_file) + # Act / Assert + # Implicit assert: no warning. + # A warning would cause a failure. + Image.open(test_file) + def test_warning(self): + # Arrange + # Set limit to a low, easily testable value + Image.MAX_IMAGE_PIXELS = 10 + self.assertEqual(Image.MAX_IMAGE_PIXELS, 10) -def test_warning(): - # Arrange - # Set limit to a low, easily testable value - Image.MAX_IMAGE_PIXELS = 10 - assert_equal(Image.MAX_IMAGE_PIXELS, 10) + # Act / Assert + self.assert_warning( + Image.DecompressionBombWarning, + lambda: Image.open(test_file)) - # Act / Assert - assert_warning( - DecompressionBombWarning, - lambda: Image.open(test_file)) +if __name__ == '__main__': + unittest.main() # End of file From 282281f1e5778c18a7d2a9480224e3b113ec29d6 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 23 Jun 2014 11:22:25 +0300 Subject: [PATCH 149/488] Reset limit in tearDown() --- Tests/test_decompression_bomb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 1f85b1b9a..b83f33f46 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -4,9 +4,14 @@ from PIL import Image test_file = "Images/lena.ppm" +ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS + class TestDecompressionBomb(PillowTestCase): + def tearDown(self): + Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT + def test_no_warning_small_file(self): # Implicit assert: no warning. # A warning would cause a failure. From a4d9b7f54c08ad97956c52b2214adc80e0776dcb Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 23 Jun 2014 12:28:02 -0400 Subject: [PATCH 150/488] Update --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ff5a9ecb9..f8991166c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Decompression bomb protection + [hugovk] + +- Put images in a single directory + [hugovk] + - Support OpenJpeg 2.1 [wiredfool] From d1a5f3b6ef358e6229c4b28b83d537336db62232 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 23 Jun 2014 10:01:40 -0700 Subject: [PATCH 151/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f8991166c..ee4e0e8f2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,8 +11,8 @@ Changelog (Pillow) [hugovk] - Support OpenJpeg 2.1 - [wiredfool] - + [al45tair] + - Remove unistd.h #include for all platforms [wiredfool] From 66e667227acffd71d74a58e94b1192b56e6771bd Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 23 Jun 2014 10:03:06 -0700 Subject: [PATCH 152/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ee4e0e8f2..3bf9282c4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,7 +12,7 @@ Changelog (Pillow) - Support OpenJpeg 2.1 [al45tair] - + - Remove unistd.h #include for all platforms [wiredfool] From 5522fa472da252bf39b5e30b6d267d8340653731 Mon Sep 17 00:00:00 2001 From: Terseus Date: Mon, 23 Jun 2014 20:07:09 +0200 Subject: [PATCH 153/488] Fixed variables misprint in ImageDraw tests --- Tests/test_imagedraw.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index a19ba78f8..ccd78e4e2 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -123,7 +123,7 @@ class TestImageDraw(PillowTestCase): draw = ImageDraw.Draw(im) # Act - draw.line(points1, fill="yellow", width=2) + draw.line(points, fill="yellow", width=2) del draw # Assert @@ -161,7 +161,7 @@ class TestImageDraw(PillowTestCase): draw = ImageDraw.Draw(im) # Act - draw.point(points1, fill="yellow") + draw.point(points, fill="yellow") del draw # Assert @@ -180,7 +180,7 @@ class TestImageDraw(PillowTestCase): draw = ImageDraw.Draw(im) # Act - draw.polygon(points1, fill="red", outline="blue") + draw.polygon(points, fill="red", outline="blue") del draw # Assert From 2ccd76e66fb1b281e297fc334e791466810e92b4 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 23 Jun 2014 14:38:03 -0400 Subject: [PATCH 154/488] Fix path --- Tests/test_decompression_bomb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index b83f33f46..4c09bd9e3 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -2,7 +2,7 @@ from helper import unittest, PillowTestCase, tearDownModule from PIL import Image -test_file = "Images/lena.ppm" +test_file = "Tests/images/lena.ppm" ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS From b7f14415d82f1f077b928ea60799cc3f7aa2f574 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 23 Jun 2014 22:51:31 +0300 Subject: [PATCH 155/488] Constants are now ALL CAPS --- Tests/test_imagedraw.py | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ccd78e4e2..996c45e79 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -7,21 +7,21 @@ from PIL import ImageDraw import sys # Image size -w, h = 100, 100 +W, H = 100, 100 # Bounding box points -x0 = int(w / 4) -x1 = int(x0 * 3) -y0 = int(h / 4) -y1 = int(x0 * 3) +X0 = int(W / 4) +X1 = int(X0 * 3) +Y0 = int(H / 4) +Y1 = int(X0 * 3) # Two kinds of bounding box -bbox1 = [(x0, y0), (x1, y1)] -bbox2 = [x0, y0, x1, y1] +BBOX1 = [(X0, Y0), (X1, Y1)] +BBOX2 = [X0, Y0, X1, Y1] # Two kinds of coordinate sequences -points1 = [(10, 10), (20, 40), (30, 30)] -points2 = [10, 10, 20, 40, 30, 30] +POINTS1 = [(10, 10), (20, 40), (30, 30)] +POINTS2 = [10, 10, 20, 40, 30, 30] class TestImageDraw(PillowTestCase): @@ -47,7 +47,7 @@ class TestImageDraw(PillowTestCase): def helper_arc(self, bbox): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -60,15 +60,15 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_arc.png")) def test_arc1(self): - self.helper_arc(bbox1) + self.helper_arc(BBOX1) def test_arc2(self): - self.helper_arc(bbox2) + self.helper_arc(BBOX2) def test_bitmap(self): # Arrange small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -81,7 +81,7 @@ class TestImageDraw(PillowTestCase): def helper_chord(self, bbox): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -93,14 +93,14 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_chord.png")) def test_chord1(self): - self.helper_chord(bbox1) + self.helper_chord(BBOX1) def test_chord2(self): - self.helper_chord(bbox2) + self.helper_chord(BBOX2) def helper_ellipse(self, bbox): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -112,14 +112,14 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_ellipse.png")) def test_ellipse1(self): - self.helper_ellipse(bbox1) + self.helper_ellipse(BBOX1) def test_ellipse2(self): - self.helper_ellipse(bbox2) + self.helper_ellipse(BBOX2) def helper_line(self, points): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -131,14 +131,14 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_line.png")) def test_line1(self): - self.helper_line(points1) + self.helper_line(POINTS1) def test_line2(self): - self.helper_line(points2) + self.helper_line(POINTS2) def helper_pieslice(self, bbox): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -150,14 +150,14 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_pieslice.png")) def test_pieslice1(self): - self.helper_pieslice(bbox1) + self.helper_pieslice(BBOX1) def test_pieslice2(self): - self.helper_pieslice(bbox2) + self.helper_pieslice(BBOX2) def helper_point(self, points): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -169,14 +169,14 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_point.png")) def test_point1(self): - self.helper_point(points1) + self.helper_point(POINTS1) def test_point2(self): - self.helper_point(points2) + self.helper_point(POINTS2) def helper_polygon(self, points): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -188,14 +188,14 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_polygon.png")) def test_polygon1(self): - self.helper_polygon(points1) + self.helper_polygon(POINTS1) def test_polygon2(self): - self.helper_polygon(points2) + self.helper_polygon(POINTS2) def helper_rectangle(self, bbox): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) # Act @@ -207,17 +207,17 @@ class TestImageDraw(PillowTestCase): im, Image.open("Tests/images/imagedraw_rectangle.png")) def test_rectangle1(self): - self.helper_rectangle(bbox1) + self.helper_rectangle(BBOX1) def test_rectangle2(self): - self.helper_rectangle(bbox2) + self.helper_rectangle(BBOX2) def test_floodfill(self): # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) + draw.rectangle(BBOX2, outline="yellow", fill="green") + centre_point = (int(W/2), int(H/2)) # Act ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) @@ -233,10 +233,10 @@ class TestImageDraw(PillowTestCase): # floodfill() is experimental # Arrange - im = Image.new("RGB", (w, h)) + im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - draw.rectangle(bbox2, outline="yellow", fill="green") - centre_point = (int(w/2), int(h/2)) + draw.rectangle(BBOX2, outline="yellow", fill="green") + centre_point = (int(W/2), int(H/2)) # Act ImageDraw.floodfill( From eda4864b62b2aa33ee3daa0790382ca11f410b99 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 23 Jun 2014 16:02:29 -0700 Subject: [PATCH 156/488] send a bytes object into the c layer instead of a bytearray, which is unimplemented in pypy --- PIL/ImageMorph.py | 4 ++-- _imagingmorph.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 7996a48aa..ca9b3bb80 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -195,7 +195,7 @@ class MorphOp: raise Exception('No operator loaded') outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply(self.lut, image.im.id, outimage.im.id) + count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) return count, outimage def match(self, image): @@ -205,7 +205,7 @@ class MorphOp: if self.lut is None: raise Exception('No operator loaded') - return _imagingmorph.match(self.lut, image.im.id) + return _imagingmorph.match(bytes(self.lut), image.im.id) def get_on_pixels(self, image): """Get a list of all turned on pixels in a binary image diff --git a/_imagingmorph.c b/_imagingmorph.c index 68a1db35b..7e7fdd879 100644 --- a/_imagingmorph.c +++ b/_imagingmorph.c @@ -45,19 +45,19 @@ apply(PyObject *self, PyObject* args) return NULL; } - if (!PyByteArray_Check(py_lut)) { - PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a byte array"); + if (!PyBytes_Check(py_lut)) { + PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); return NULL; } - lut_len = PyByteArray_Size(py_lut); + lut_len = PyBytes_Size(py_lut); if (lut_len < LUT_SIZE) { PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } - lut = PyByteArray_AsString(py_lut); + lut = PyBytes_AsString(py_lut); imgin = (Imaging) i0; imgout = (Imaging) i1; @@ -152,19 +152,19 @@ match(PyObject *self, PyObject* args) return NULL; } - if (!PyByteArray_Check(py_lut)) { - PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a byte array"); + if (!PyBytes_Check(py_lut)) { + PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); return NULL; } - lut_len = PyByteArray_Size(py_lut); + lut_len = PyBytes_Size(py_lut); if (lut_len < LUT_SIZE) { PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } - lut = PyByteArray_AsString(py_lut); + lut = PyBytes_AsString(py_lut); imgin = (Imaging) i0; if (imgin->type != IMAGING_TYPE_UINT8 && From cf828f7612eddb4b33eb4edeee8fd92b1e6cc14f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 23 Jun 2014 16:52:10 -0700 Subject: [PATCH 157/488] Converted to unittest framework to deal with test framework error --- Tests/test_imagemorph.py | 301 ++++++++++++++++++++------------------- 1 file changed, 153 insertions(+), 148 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index a5e28cd7e..c0f8b4783 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,164 +1,169 @@ # Test the ImageMorphology functionality -from tester import * +from helper import * from PIL import Image from PIL import ImageMorph -def img_to_string(im): - """Turn a (small) binary image into a string representation""" - chars = '.1' - width, height = im.size - return '\n'.join( - [''.join([chars[im.getpixel((c,r))>0] for c in range(width)]) - for r in range(height)]) -def string_to_img(image_string): - """Turn a string image representation into a binary image""" - rows = [s for s in image_string.replace(' ','').split('\n') - if len(s)] - height = len(rows) - width = len(rows[0]) - im = Image.new('L',(width,height)) - for i in range(width): - for j in range(height): - c = rows[j][i] - v = c in 'X1' - im.putpixel((i,j),v) +class MorphTests(PillowTestCase): - return im - -def img_string_normalize(im): - return img_to_string(string_to_img(im)) - -def assert_img_equal(A,B): - assert_equal(img_to_string(A), img_to_string(B)) - -def assert_img_equal_img_string(A,Bstring): - assert_equal(img_to_string(A), img_string_normalize(Bstring)) - -A = string_to_img( -""" -....... -....... -..111.. -..111.. -..111.. -....... -....... -""" -) - -def test_str_to_img(): - im = Image.open('Tests/images/morph_a.png') - assert_image_equal(A, im) - -def create_lut(): - for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): - lb = ImageMorph.LutBuilder(op_name=op) - lut = lb.build_lut() - with open('Tests/images/%s.lut' % op, 'wb') as f: - f.write(lut) - -#create_lut() -def test_lut(): - for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): - lb = ImageMorph.LutBuilder(op_name=op) - lut = lb.build_lut() - with open('Tests/images/%s.lut' % op , 'rb') as f: - assert_equal(lut, bytearray(f.read())) - + def setUp(self): + self.A = self.string_to_img( + """ + ....... + ....... + ..111.. + ..111.. + ..111.. + ....... + ....... + """ + ) -# Test the named patterns -def test_erosion8(): - # erosion8 - mop = ImageMorph.MorphOp(op_name='erosion8') - count,Aout = mop.apply(A) - assert_equal(count,8) - assert_img_equal_img_string(Aout, - """ - ....... - ....... - ....... - ...1... - ....... - ....... - ....... - """) -def test_dialation8(): - # dialation8 - mop = ImageMorph.MorphOp(op_name='dilation8') - count,Aout = mop.apply(A) - assert_equal(count,16) - assert_img_equal_img_string(Aout, - """ - ....... - .11111. - .11111. - .11111. - .11111. - .11111. - ....... - """) + def img_to_string(self, im): + """Turn a (small) binary image into a string representation""" + chars = '.1' + width, height = im.size + return '\n'.join( + [''.join([chars[im.getpixel((c,r))>0] for c in range(width)]) + for r in range(height)]) -def test_erosion4(): - # erosion4 - mop = ImageMorph.MorphOp(op_name='dilation4') - count,Aout = mop.apply(A) - assert_equal(count,12) - assert_img_equal_img_string(Aout, - """ - ....... - ..111.. - .11111. - .11111. - .11111. - ..111.. - ....... - """) + def string_to_img(self, image_string): + """Turn a string image representation into a binary image""" + rows = [s for s in image_string.replace(' ','').split('\n') + if len(s)] + height = len(rows) + width = len(rows[0]) + im = Image.new('L',(width,height)) + for i in range(width): + for j in range(height): + c = rows[j][i] + v = c in 'X1' + im.putpixel((i,j),v) -def test_edge(): - # edge - mop = ImageMorph.MorphOp(op_name='edge') - count,Aout = mop.apply(A) - assert_equal(count,1) - assert_img_equal_img_string(Aout, - """ - ....... - ....... - ..111.. - ..1.1.. - ..111.. - ....... - ....... - """) + return im + + def img_string_normalize(self, im): + return self.img_to_string(self.string_to_img(im)) + + def assert_img_equal(self, A, B): + self.assertEqual(self.img_to_string(A), self.img_to_string(B)) + + def assert_img_equal_img_string(self, A, Bstring): + self.assertEqual(self.img_to_string(A), self.img_string_normalize(Bstring)) + + def test_str_to_img(self): + im = Image.open('Tests/images/morph_a.png') + self.assert_image_equal(self.A, im) + + def create_lut(self): + for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): + lb = ImageMorph.LutBuilder(op_name=op) + lut = lb.build_lut(self) + with open('Tests/images/%s.lut' % op, 'wb') as f: + f.write(lut) + + #create_lut() + def test_lut(self): + for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): + lb = ImageMorph.LutBuilder(op_name=op) + lut = lb.build_lut() + with open('Tests/images/%s.lut' % op , 'rb') as f: + self.assertEqual(lut, bytearray(f.read())) -def test_corner(): - # Create a corner detector pattern - mop = ImageMorph.MorphOp(patterns = ['1:(... ... ...)->0', - '4:(00. 01. ...)->1']) - count,Aout = mop.apply(A) - assert_equal(count,5) - assert_img_equal_img_string(Aout, - """ - ....... - ....... - ..1.1.. - ....... - ..1.1.. - ....... - ....... - """) + # Test the named patterns + def test_erosion8(self): + # erosion8 + mop = ImageMorph.MorphOp(op_name='erosion8') + count,Aout = mop.apply(self.A) + self.assertEqual(count,8) + self.assert_img_equal_img_string(Aout, + """ + ....... + ....... + ....... + ...1... + ....... + ....... + ....... + """) + + def test_dialation8(self): + # dialation8 + mop = ImageMorph.MorphOp(op_name='dilation8') + count,Aout = mop.apply(self.A) + self.assertEqual(count,16) + self.assert_img_equal_img_string(Aout, + """ + ....... + .11111. + .11111. + .11111. + .11111. + .11111. + ....... + """) + + def test_erosion4(self): + # erosion4 + mop = ImageMorph.MorphOp(op_name='dilation4') + count,Aout = mop.apply(self.A) + self.assertEqual(count,12) + self.assert_img_equal_img_string(Aout, + """ + ....... + ..111.. + .11111. + .11111. + .11111. + ..111.. + ....... + """) + + def test_edge(self): + # edge + mop = ImageMorph.MorphOp(op_name='edge') + count,Aout = mop.apply(self.A) + self.assertEqual(count,1) + self.assert_img_equal_img_string(Aout, + """ + ....... + ....... + ..111.. + ..1.1.. + ..111.. + ....... + ....... + """) - # Test the coordinate counting with the same operator - coords = mop.match(A) - assert_equal(len(coords), 4) - assert_equal(tuple(coords), - ((2,2),(4,2),(2,4),(4,4))) + def test_corner(self): + # Create a corner detector pattern + mop = ImageMorph.MorphOp(patterns = ['1:(... ... ...)->0', + '4:(00. 01. ...)->1']) + count,Aout = mop.apply(self.A) + self.assertEqual(count,5) + self.assert_img_equal_img_string(Aout, + """ + ....... + ....... + ..1.1.. + ....... + ..1.1.. + ....... + ....... + """) + - coords = mop.get_on_pixels(Aout) - assert_equal(len(coords), 4) - assert_equal(tuple(coords), - ((2,2),(4,2),(2,4),(4,4))) + # Test the coordinate counting with the same operator + coords = mop.match(self.A) + self.assertEqual(len(coords), 4) + self.assertEqual(tuple(coords), + ((2,2),(4,2),(2,4),(4,4))) + + coords = mop.get_on_pixels(Aout) + self.assertEqual(len(coords), 4) + self.assertEqual(tuple(coords), + ((2,2),(4,2),(2,4),(4,4))) From a8cfe52a0ba4d8714b50655a3ef0856b1153b66c Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 23 Jun 2014 20:12:37 -0400 Subject: [PATCH 158/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3bf9282c4..e63966bf0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Add binary morphology addon + [dov, wiredfool] + - Decompression bomb protection [hugovk] From fd97d30831a39c35c0a228995a4061568cfcb0c2 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 09:34:05 +0300 Subject: [PATCH 159/488] flake8 on morphology changes --- PIL/ImageMorph.py | 132 +++++++++++++++++++++------------------ Tests/test_imagemorph.py | 66 ++++++++++---------- setup.py | 73 ++++++++++++++-------- 3 files changed, 151 insertions(+), 120 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index ca9b3bb80..fd14b452c 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -9,9 +9,11 @@ from PIL import Image from PIL import _imagingmorph import re -LUT_SIZE = 1<<9 +LUT_SIZE = 1 << 9 + + class LutBuilder: - """A class for building MorphLut's from a descriptive language + """A class for building a MorphLut from a descriptive language The input patterns is a list of a strings sequences like these: @@ -20,8 +22,8 @@ class LutBuilder: 111)->1 (whitespaces including linebreaks are ignored). The option 4 - descibes a series of symmetry operations (in this case a - 4-rotation), the pattern is decribed by: + describes a series of symmetry operations (in this case a + 4-rotation), the pattern is described by: . or X - Ignore 1 - Pixel is on @@ -42,9 +44,9 @@ class LutBuilder: lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) lut = lb.build_lut() - + """ - def __init__(self,patterns = None,op_name=None): + def __init__(self, patterns=None, op_name=None): if patterns is not None: self.patterns = patterns else: @@ -52,19 +54,19 @@ class LutBuilder: self.lut = None if op_name is not None: known_patterns = { - 'corner' : ['1:(... ... ...)->0', - '4:(00. 01. ...)->1'], - 'dilation4' : ['4:(... .0. .1.)->1'], - 'dilation8' : ['4:(... .0. .1.)->1', - '4:(... .0. ..1)->1'], - 'erosion4' : ['4:(... .1. .0.)->0'], - 'erosion8' : ['4:(... .1. .0.)->0', - '4:(... .1. ..0)->0'], - 'edge' : ['1:(... ... ...)->0', - '4:(.0. .1. ...)->1', - '4:(01. .1. ...)->1'] + 'corner': ['1:(... ... ...)->0', + '4:(00. 01. ...)->1'], + 'dilation4': ['4:(... .0. .1.)->1'], + 'dilation8': ['4:(... .0. .1.)->1', + '4:(... .0. ..1)->1'], + 'erosion4': ['4:(... .1. .0.)->0'], + 'erosion8': ['4:(... .1. .0.)->0', + '4:(... .1. ..0)->0'], + 'edge': ['1:(... ... ...)->0', + '4:(.0. .1. ...)->1', + '4:(01. .1. ...)->1'] } - if not op_name in known_patterns: + if op_name not in known_patterns: raise Exception('Unknown pattern '+op_name+'!') self.patterns = known_patterns[op_name] @@ -75,21 +77,21 @@ class LutBuilder: def build_default_lut(self): symbols = [0, 1] m = 1 << 4 # pos of current pixel - self.lut = bytearray([symbols[(i & m)>0] for i in range(LUT_SIZE)]) + self.lut = bytearray([symbols[(i & m) > 0] for i in range(LUT_SIZE)]) def get_lut(self): return self.lut def _string_permute(self, pattern, permutation): """string_permute takes a pattern and a permutation and returns the - string permuted accordinging to the permutation list. + string permuted according to the permutation list. """ - assert(len(permutation)==9) + assert(len(permutation) == 9) return ''.join([pattern[p] for p in permutation]) def _pattern_permute(self, basic_pattern, options, basic_result): """pattern_permute takes a basic pattern and its result and clones - the mattern according to the modifications described in the $options + the pattern according to the modifications described in the $options parameter. It returns a list of all cloned patterns.""" patterns = [(basic_pattern, basic_result)] @@ -98,29 +100,28 @@ class LutBuilder: res = patterns[-1][1] for i in range(4): patterns.append( - (self._string_permute(patterns[-1][0], - [6,3,0, - 7,4,1, - 8,5,2]), res)) + (self._string_permute(patterns[-1][0], [6, 3, 0, + 7, 4, 1, + 8, 5, 2]), res)) # mirror if 'M' in options: n = len(patterns) - for pattern,res in patterns[0:n]: + for pattern, res in patterns[0:n]: patterns.append( - (self._string_permute(pattern, [2,1,0, - 5,4,3, - 8,7,6]), res)) + (self._string_permute(pattern, [2, 1, 0, + 5, 4, 3, + 8, 7, 6]), res)) # negate if 'N' in options: n = len(patterns) - for pattern,res in patterns[0:n]: + for pattern, res in patterns[0:n]: # Swap 0 and 1 pattern = (pattern - .replace('0','Z') - .replace('1','0') - .replace('Z','1')) - res = '%d'%(1-int(res)) + .replace('0', 'Z') + .replace('1', '0') + .replace('Z', '1')) + res = '%d' % (1-int(res)) patterns.append((pattern, res)) return patterns @@ -135,7 +136,8 @@ class LutBuilder: # Parse and create symmetries of the patterns strings for p in self.patterns: - m = re.search(r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n','')) + m = re.search( + r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n', '')) if not m: raise Exception('Syntax error in pattern "'+p+'"') options = m.group(1) @@ -143,7 +145,7 @@ class LutBuilder: result = int(m.group(3)) # Get rid of spaces - pattern= pattern.replace(' ','').replace('\n','') + pattern = pattern.replace(' ', '').replace('\n', '') patterns += self._pattern_permute(pattern, options, result) @@ -154,7 +156,7 @@ class LutBuilder: # compile the patterns into regular expressions for speed for i in range(len(patterns)): - p = patterns[i][0].replace('.','X').replace('X','[01]') + p = patterns[i][0].replace('.', 'X').replace('X', '[01]') p = re.compile(p) patterns[i] = (p, patterns[i][1]) @@ -166,25 +168,26 @@ class LutBuilder: bitpattern = bin(i)[2:] bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1] - for p,r in patterns: + for p, r in patterns: if p.match(bitpattern): self.lut[i] = [0, 1][r] return self.lut - + + class MorphOp: """A class for binary morphological operators""" def __init__(self, lut=None, - op_name = None, - patterns = None): + op_name=None, + patterns=None): """Create a binary morphological operator""" self.lut = lut if op_name is not None: - self.lut = LutBuilder(op_name = op_name).build_lut() + self.lut = LutBuilder(op_name=op_name).build_lut() elif patterns is not None: - self.lut = LutBuilder(patterns = patterns).build_lut() + self.lut = LutBuilder(patterns=patterns).build_lut() def apply(self, image): """Run a single morphological operation on an image @@ -193,32 +196,37 @@ class MorphOp: morphed image""" if self.lut is None: raise Exception('No operator loaded') - - outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) - return count, outimage - - def match(self, image): - """Get a list of coordinates matching the morphological operation on an image - Returns a list of tuples of (x,y) coordinates of all matching pixels.""" + outimage = Image.new(image.mode, image.size, None) + count = _imagingmorph.apply( + bytes(self.lut), image.im.id, outimage.im.id) + return count, outimage + + def match(self, image): + """Get a list of coordinates matching the morphological operation on + an image. + + Returns a list of tuples of (x,y) coordinates + of all matching pixels.""" if self.lut is None: raise Exception('No operator loaded') - - return _imagingmorph.match(bytes(self.lut), image.im.id) + + return _imagingmorph.match(bytes(self.lut), image.im.id) def get_on_pixels(self, image): """Get a list of all turned on pixels in a binary image - Returns a list of tuples of (x,y) coordinates of all matching pixels.""" - - return _imagingmorph.get_on_pixels(image.im.id) + + Returns a list of tuples of (x,y) coordinates + of all matching pixels.""" + + return _imagingmorph.get_on_pixels(image.im.id) def load_lut(self, filename): """Load an operator from an mrl file""" - with open(filename,'rb') as f: + with open(filename, 'rb') as f: self.lut = bytearray(f.read()) - - if len(self.lut)!= 8192: + + if len(self.lut) != 8192: self.lut = None raise Exception('Wrong size operator file!') @@ -226,11 +234,11 @@ class MorphOp: """Load an operator save mrl file""" if self.lut is None: raise Exception('No operator loaded') - with open(filename,'wb') as f: + with open(filename, 'wb') as f: f.write(self.lut) def set_lut(self, lut): """Set the lut from an external source""" self.lut = lut - +# End of file diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index c0f8b4783..0ff32eb42 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -9,7 +9,7 @@ class MorphTests(PillowTestCase): def setUp(self): self.A = self.string_to_img( - """ + """ ....... ....... ..111.. @@ -18,29 +18,28 @@ class MorphTests(PillowTestCase): ....... ....... """ - ) - + ) def img_to_string(self, im): """Turn a (small) binary image into a string representation""" chars = '.1' width, height = im.size return '\n'.join( - [''.join([chars[im.getpixel((c,r))>0] for c in range(width)]) + [''.join([chars[im.getpixel((c, r)) > 0] for c in range(width)]) for r in range(height)]) def string_to_img(self, image_string): """Turn a string image representation into a binary image""" - rows = [s for s in image_string.replace(' ','').split('\n') + rows = [s for s in image_string.replace(' ', '').split('\n') if len(s)] height = len(rows) width = len(rows[0]) - im = Image.new('L',(width,height)) + im = Image.new('L', (width, height)) for i in range(width): for j in range(height): c = rows[j][i] v = c in 'X1' - im.putpixel((i,j),v) + im.putpixel((i, j), v) return im @@ -51,34 +50,39 @@ class MorphTests(PillowTestCase): self.assertEqual(self.img_to_string(A), self.img_to_string(B)) def assert_img_equal_img_string(self, A, Bstring): - self.assertEqual(self.img_to_string(A), self.img_string_normalize(Bstring)) + self.assertEqual( + self.img_to_string(A), + self.img_string_normalize(Bstring)) def test_str_to_img(self): im = Image.open('Tests/images/morph_a.png') self.assert_image_equal(self.A, im) def create_lut(self): - for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): + for op in ( + 'corner', 'dilation4', 'dilation8', + 'erosion4', 'erosion8', 'edge'): lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut(self) with open('Tests/images/%s.lut' % op, 'wb') as f: f.write(lut) - #create_lut() + # create_lut() def test_lut(self): - for op in ('corner', 'dilation4', 'dilation8', 'erosion4', 'erosion8', 'edge'): + for op in ( + 'corner', 'dilation4', 'dilation8', + 'erosion4', 'erosion8', 'edge'): lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut() - with open('Tests/images/%s.lut' % op , 'rb') as f: + with open('Tests/images/%s.lut' % op, 'rb') as f: self.assertEqual(lut, bytearray(f.read())) - # Test the named patterns def test_erosion8(self): # erosion8 mop = ImageMorph.MorphOp(op_name='erosion8') - count,Aout = mop.apply(self.A) - self.assertEqual(count,8) + count, Aout = mop.apply(self.A) + self.assertEqual(count, 8) self.assert_img_equal_img_string(Aout, """ ....... @@ -93,8 +97,8 @@ class MorphTests(PillowTestCase): def test_dialation8(self): # dialation8 mop = ImageMorph.MorphOp(op_name='dilation8') - count,Aout = mop.apply(self.A) - self.assertEqual(count,16) + count, Aout = mop.apply(self.A) + self.assertEqual(count, 16) self.assert_img_equal_img_string(Aout, """ ....... @@ -109,8 +113,8 @@ class MorphTests(PillowTestCase): def test_erosion4(self): # erosion4 mop = ImageMorph.MorphOp(op_name='dilation4') - count,Aout = mop.apply(self.A) - self.assertEqual(count,12) + count, Aout = mop.apply(self.A) + self.assertEqual(count, 12) self.assert_img_equal_img_string(Aout, """ ....... @@ -123,10 +127,10 @@ class MorphTests(PillowTestCase): """) def test_edge(self): - # edge + # edge mop = ImageMorph.MorphOp(op_name='edge') - count,Aout = mop.apply(self.A) - self.assertEqual(count,1) + count, Aout = mop.apply(self.A) + self.assertEqual(count, 1) self.assert_img_equal_img_string(Aout, """ ....... @@ -138,13 +142,12 @@ class MorphTests(PillowTestCase): ....... """) - def test_corner(self): # Create a corner detector pattern - mop = ImageMorph.MorphOp(patterns = ['1:(... ... ...)->0', - '4:(00. 01. ...)->1']) - count,Aout = mop.apply(self.A) - self.assertEqual(count,5) + mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0', + '4:(00. 01. ...)->1']) + count, Aout = mop.apply(self.A) + self.assertEqual(count, 5) self.assert_img_equal_img_string(Aout, """ ....... @@ -155,15 +158,14 @@ class MorphTests(PillowTestCase): ....... ....... """) - # Test the coordinate counting with the same operator coords = mop.match(self.A) self.assertEqual(len(coords), 4) - self.assertEqual(tuple(coords), - ((2,2),(4,2),(2,4),(4,4))) + self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4))) coords = mop.get_on_pixels(Aout) self.assertEqual(len(coords), 4) - self.assertEqual(tuple(coords), - ((2,2),(4,2),(2,4),(4,4))) + self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4))) + +# End of file diff --git a/setup.py b/setup.py index 57a5821cd..2fbcc2959 100644 --- a/setup.py +++ b/setup.py @@ -106,6 +106,7 @@ class pil_build_ext(build_ext): def require(self, feat): return feat in self.required + def want(self, feat): return getattr(self, feat) is None @@ -137,8 +138,8 @@ class pil_build_ext(build_ext): setattr(self.feature, x, False) if getattr(self, 'enable_%s' % x): raise ValueError( - 'Conflicting options: --enable-%s and --disable-%s' - % (x, x)) + 'Conflicting options: --enable-%s and --disable-%s' + % (x, x)) if getattr(self, 'enable_%s' % x): self.feature.required.append(x) @@ -225,15 +226,17 @@ class pil_build_ext(build_ext): if ft_prefix and os.path.isdir(ft_prefix): # freetype might not be linked into Homebrew's prefix _add_directory(library_dirs, os.path.join(ft_prefix, 'lib')) - _add_directory(include_dirs, os.path.join(ft_prefix, 'include')) + _add_directory( + include_dirs, os.path.join(ft_prefix, 'include')) else: - # fall back to freetype from XQuartz if Homebrew's freetype is missing + # fall back to freetype from XQuartz if + # Homebrew's freetype is missing _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") elif sys.platform.startswith("linux"): arch_tp = (plat.processor(), plat.architecture()[0]) - if arch_tp == ("x86_64","32bit"): + if arch_tp == ("x86_64", "32bit"): # 32 bit build on 64 bit machine. _add_directory(library_dirs, "/usr/lib/i386-linux-gnu") else: @@ -245,30 +248,38 @@ class pil_build_ext(build_ext): if platform_ in ["x86_64", "64bit"]: _add_directory(library_dirs, "/lib64") _add_directory(library_dirs, "/usr/lib64") - _add_directory(library_dirs, "/usr/lib/x86_64-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/x86_64-linux-gnu") break elif platform_ in ["i386", "i686", "32bit"]: - _add_directory(library_dirs, "/usr/lib/i386-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/i386-linux-gnu") break elif platform_ in ["aarch64"]: _add_directory(library_dirs, "/usr/lib64") - _add_directory(library_dirs, "/usr/lib/aarch64-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/aarch64-linux-gnu") break elif platform_ in ["arm", "armv7l"]: - _add_directory(library_dirs, "/usr/lib/arm-linux-gnueabi") + _add_directory( + library_dirs, "/usr/lib/arm-linux-gnueabi") break elif platform_ in ["ppc64"]: _add_directory(library_dirs, "/usr/lib64") - _add_directory(library_dirs, "/usr/lib/ppc64-linux-gnu") - _add_directory(library_dirs, "/usr/lib/powerpc64-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/ppc64-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/powerpc64-linux-gnu") break elif platform_ in ["ppc"]: _add_directory(library_dirs, "/usr/lib/ppc-linux-gnu") - _add_directory(library_dirs, "/usr/lib/powerpc-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/powerpc-linux-gnu") break elif platform_ in ["s390x"]: _add_directory(library_dirs, "/usr/lib64") - _add_directory(library_dirs, "/usr/lib/s390x-linux-gnu") + _add_directory( + library_dirs, "/usr/lib/s390x-linux-gnu") break elif platform_ in ["s390"]: _add_directory(library_dirs, "/usr/lib/s390-linux-gnu") @@ -344,7 +355,8 @@ class pil_build_ext(build_ext): best_path = None for name in os.listdir(program_files): if name.startswith('OpenJPEG '): - version = tuple([int(x) for x in name[9:].strip().split('.')]) + version = tuple( + [int(x) for x in name[9:].strip().split('.')]) if version > best_version: best_version = version best_path = os.path.join(program_files, name) @@ -371,7 +383,8 @@ class pil_build_ext(build_ext): if _find_include_file(self, "zlib.h"): if _find_library_file(self, "z"): feature.zlib = "z" - elif sys.platform == "win32" and _find_library_file(self, "zlib"): + elif (sys.platform == "win32" and + _find_library_file(self, "zlib")): feature.zlib = "zlib" # alternative name if feature.want('jpeg'): @@ -387,7 +400,7 @@ class pil_build_ext(build_ext): if feature.want('jpeg2000'): best_version = None best_path = None - + # Find the best version for directory in self.compiler.include_dirs: for name in os.listdir(directory): @@ -405,14 +418,16 @@ class pil_build_ext(build_ext): # include path _add_directory(self.compiler.include_dirs, best_path, 0) feature.jpeg2000 = 'openjp2' - feature.openjpeg_version = '.'.join([str(x) for x in best_version]) - + feature.openjpeg_version = '.'.join( + [str(x) for x in best_version]) + if feature.want('tiff'): if _find_library_file(self, "tiff"): feature.tiff = "tiff" if sys.platform == "win32" and _find_library_file(self, "libtiff"): feature.tiff = "libtiff" - if sys.platform == "darwin" and _find_library_file(self, "libtiff"): + if (sys.platform == "darwin" and + _find_library_file(self, "libtiff")): feature.tiff = "libtiff" if feature.want('freetype'): @@ -459,20 +474,22 @@ class pil_build_ext(build_ext): if feature.want('webp'): if (_find_include_file(self, "webp/encode.h") and _find_include_file(self, "webp/decode.h")): - if _find_library_file(self, "webp"): # in googles precompiled zip it is call "libwebp" + # In Google's precompiled zip it is call "libwebp": + if _find_library_file(self, "webp"): feature.webp = "webp" if feature.want('webpmux'): if (_find_include_file(self, "webp/mux.h") and _find_include_file(self, "webp/demux.h")): - if _find_library_file(self, "webpmux") and _find_library_file(self, "webpdemux"): + if (_find_library_file(self, "webpmux") and + _find_library_file(self, "webpdemux")): feature.webpmux = "webpmux" for f in feature: if not getattr(feature, f) and feature.require(f): raise ValueError( - '--enable-%s requested but %s not found, aborting.' - % (f, f)) + '--enable-%s requested but %s not found, aborting.' + % (f, f)) # # core library @@ -527,7 +544,9 @@ class pil_build_ext(build_ext): if sys.platform == "win32": extra.extend(["user32", "gdi32"]) exts.append(Extension( - "PIL._imagingcms", ["_imagingcms.c"], libraries=["lcms2"] + extra)) + "PIL._imagingcms", + ["_imagingcms.c"], + libraries=["lcms2"] + extra)) if os.path.isfile("_webp.c") and feature.webp: libs = ["webp"] @@ -603,7 +622,8 @@ class pil_build_ext(build_ext): options = [ (feature.tcl and feature.tk, "TKINTER"), (feature.jpeg, "JPEG"), - (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), + (feature.jpeg2000, "OPENJPEG (JPEG2000)", + feature.openjpeg_version), (feature.zlib, "ZLIB (PNG/ZIP)"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), @@ -715,8 +735,9 @@ setup( packages=find_packages(), scripts=glob.glob("Scripts/pil*.py"), test_suite='PIL.tests', - keywords=["Imaging",], + keywords=["Imaging", ], license='Standard PIL License', zip_safe=True, ) +# End of file From 540477b066341db94dfb32a4fd2df8264bf02085 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 09:51:42 +0300 Subject: [PATCH 160/488] Update a docstring [CI skip] --- PIL/ImageMorph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index fd14b452c..b24dd134f 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -231,7 +231,7 @@ class MorphOp: raise Exception('Wrong size operator file!') def save_lut(self, filename): - """Load an operator save mrl file""" + """Save an operator to an mrl file""" if self.lut is None: raise Exception('No operator loaded') with open(filename, 'wb') as f: From 494bffd216e556cd274409ecfbd37d91a34877ee Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 10:01:43 +0300 Subject: [PATCH 161/488] Make test runnable on its own --- Tests/test_imagemorph.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 0ff32eb42..a52111431 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -168,4 +168,8 @@ class MorphTests(PillowTestCase): self.assertEqual(len(coords), 4) self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4))) + +if __name__ == '__main__': + unittest.main() + # End of file From e9821edd94020865736327e4452de566b2995868 Mon Sep 17 00:00:00 2001 From: brightpisces Date: Tue, 24 Jun 2014 15:34:43 +0800 Subject: [PATCH 162/488] Match real palette format in ImagePalette.save() --- PIL/ImagePalette.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index d5b9d04eb..3496b99df 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -101,8 +101,11 @@ class ImagePalette: fp.write("# Mode: %s\n" % self.mode) for i in range(256): fp.write("%d" % i) - for j in range(i, len(self.palette), 256): - fp.write(" %d" % self.palette[j]) + for j in range(i*3, i*3+3): + try: + fp.write(" %d" % self.palette[j]) + except IndexError: + fp.write(" 0") fp.write("\n") fp.close() From 8755bda4e357d988f77b56e77ed5fc4ae8fa0106 Mon Sep 17 00:00:00 2001 From: brightpisces Date: Tue, 24 Jun 2014 16:27:35 +0800 Subject: [PATCH 163/488] Update ImagePalette.py According to __init__, using `len(self.mode)` might be better. Tested on my machine. --- PIL/ImagePalette.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 3496b99df..59886827a 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -101,7 +101,7 @@ class ImagePalette: fp.write("# Mode: %s\n" % self.mode) for i in range(256): fp.write("%d" % i) - for j in range(i*3, i*3+3): + for j in range(i*len(self.mode), (i+1)*len(self.mode)): try: fp.write(" %d" % self.palette[j]) except IndexError: From 89c40a6a7e7cf9fc245f4b2fe143f1103600d84f Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 24 Jun 2014 05:49:08 -0400 Subject: [PATCH 164/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e63966bf0..15b476ff4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Support JPEG qtables + [wiredfool] + - Add binary morphology addon [dov, wiredfool] From 78154765ce54481595c7316a371e3fd04de5a313 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 14:03:10 +0300 Subject: [PATCH 165/488] Update README and remove unnecessary, commented code from helper.py --- Tests/README.txt | 16 +++-- Tests/helper.py | 150 ++--------------------------------------------- 2 files changed, 16 insertions(+), 150 deletions(-) diff --git a/Tests/README.txt b/Tests/README.txt index 169bc4da5..84e2c4655 100644 --- a/Tests/README.txt +++ b/Tests/README.txt @@ -1,13 +1,19 @@ -Minimalistic PIL test framework. +Pillow test files. -Test scripts are named "test_xxx" and are supposed to output "ok". That's it. To run the tests:: - - python setup.py develop +Test scripts are named `test_xxx.py` and use the `unittest` module. A base class and helper functions can be found in `helper.py`. Run the tests from the root of the Pillow source distribution: python selftest.py - python Tests/run.py --installed + nosetests Tests/test_*.py + +Or with coverage: + + coverage run --append --include=PIL/* selftest.py + coverage run --append --include=PIL/* -m nose Tests/test_*.py + coverage report + coverage html + open htmlcov/index.html To run an individual test: diff --git a/Tests/helper.py b/Tests/helper.py index aacbfc009..ebd142bf7 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -160,59 +160,11 @@ class PillowTestCase(unittest.TestCase): return files[0] -# # require that deprecation warnings are triggered -# import warnings -# warnings.simplefilter('default') -# # temporarily turn off resource warnings that warn about unclosed -# # files in the test scripts. -# try: -# warnings.filterwarnings("ignore", category=ResourceWarning) -# except NameError: -# # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. -# pass +# helpers import sys py3 = (sys.version_info >= (3, 0)) -# # some test helpers -# -# _target = None -# _tempfiles = [] -# _logfile = None -# -# -# def success(): -# import sys -# success.count += 1 -# if _logfile: -# print(sys.argv[0], success.count, failure.count, file=_logfile) -# return True -# -# -# def failure(msg=None, frame=None): -# import sys -# import linecache -# failure.count += 1 -# if _target: -# if frame is None: -# frame = sys._getframe() -# while frame.f_globals.get("__name__") != _target.__name__: -# frame = frame.f_back -# location = (frame.f_code.co_filename, frame.f_lineno) -# prefix = "%s:%d: " % location -# line = linecache.getline(*location) -# print(prefix + line.strip() + " failed:") -# if msg: -# print("- " + msg) -# if _logfile: -# print(sys.argv[0], success.count, failure.count, file=_logfile) -# return False -# -# success.count = failure.count = 0 -# - - -# helpers def fromstring(data): from io import BytesIO @@ -230,6 +182,9 @@ def tostring(im, format, **options): def lena(mode="RGB", cache={}): from PIL import Image im = None + # FIXME: Implement caching to reduce reading from disk but so an original + # copy is returned each time and the cached image isn't modified by tests + # (for fast, isolated, repeatable tests). # im = cache.get(mode) if im is None: if mode == "RGB": @@ -243,99 +198,4 @@ def lena(mode="RGB", cache={}): # cache[mode] = im return im - -# def assert_image_completely_equal(a, b, msg=None): -# if a != b: -# failure(msg or "images different") -# else: -# success() -# -# -# # test runner -# -# def run(): -# global _target, _tests, run -# import sys -# import traceback -# _target = sys.modules["__main__"] -# run = None # no need to run twice -# tests = [] -# for name, value in list(vars(_target).items()): -# if name[:5] == "test_" and type(value) is type(success): -# tests.append((value.__code__.co_firstlineno, name, value)) -# tests.sort() # sort by line -# for lineno, name, func in tests: -# try: -# _tests = [] -# func() -# for func, args in _tests: -# func(*args) -# except: -# t, v, tb = sys.exc_info() -# tb = tb.tb_next -# if tb: -# failure(frame=tb.tb_frame) -# traceback.print_exception(t, v, tb) -# else: -# print("%s:%d: cannot call test function: %s" % ( -# sys.argv[0], lineno, v)) -# failure.count += 1 -# -# -# def yield_test(function, *args): -# # collect delayed/generated tests -# _tests.append((function, args)) -# -# -# def skip(msg=None): -# import os -# print("skip") -# os._exit(0) # don't run exit handlers -# -# -# def ignore(pattern): -# """Tells the driver to ignore messages matching the pattern, for the -# duration of the current test.""" -# print('ignore: %s' % pattern) -# -# -# def _setup(): -# global _logfile -# -# import sys -# if "--coverage" in sys.argv: -# # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) -# with warnings.catch_warnings(): -# warnings.simplefilter("ignore") -# import coverage -# cov = coverage.coverage(auto_data=True, include="PIL/*") -# cov.start() -# -# def report(): -# if run: -# run() -# if success.count and not failure.count: -# print("ok") -# # only clean out tempfiles if test passed -# import os -# import os.path -# import tempfile -# for file in _tempfiles: -# try: -# os.remove(file) -# except OSError: -# pass # report? -# temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') -# try: -# os.rmdir(temp_root) -# except OSError: -# pass -# -# import atexit -# atexit.register(report) -# -# if "--log" in sys.argv: -# _logfile = open("test.log", "a") -# -# -# _setup() +# End of file From ffce319b540f1e3d94a1d4a10f975641795edf9d Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 24 Jun 2014 14:04:47 +0300 Subject: [PATCH 166/488] Change Tests/README.txt to .md [CI skip] --- Tests/{README.txt => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/{README.txt => README.md} (100%) diff --git a/Tests/README.txt b/Tests/README.md similarity index 100% rename from Tests/README.txt rename to Tests/README.md From 5b4d5148af342078a0612811fc749945c18c5984 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 24 Jun 2014 09:58:53 -0700 Subject: [PATCH 167/488] Added roundtrip test for ImagePalette.save --- Tests/test_imagepalette.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 4d7e067f4..3ee7ee869 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -26,15 +26,15 @@ class TestImagePalette(PillowTestCase): def test_file(self): - palette = ImagePalette() + palette = ImagePalette("RGB", list(range(256))*3) - file = self.tempfile("temp.lut") + f = self.tempfile("temp.lut") - palette.save(file) + palette.save(f) from PIL.ImagePalette import load, raw - p = load(file) + p = load(f) # load returns raw palette information self.assertEqual(len(p[0]), 768) @@ -42,7 +42,7 @@ class TestImagePalette(PillowTestCase): p = raw(p[1], p[0]) self.assertIsInstance(p, ImagePalette) - + self.assertEqual(p.palette, palette.tobytes()) if __name__ == '__main__': unittest.main() From 964935fb7585dd1757936f14857a39608f54a817 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 24 Jun 2014 11:03:13 -0700 Subject: [PATCH 168/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 15b476ff4..06f6e7090 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,8 +4,11 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Fixed ImagePalette.save + [brightpisces] + - Support JPEG qtables - [wiredfool] + [csinchok] - Add binary morphology addon [dov, wiredfool] From 2be4e9f3e5f433aa34e73ed225837ca0ebd06a5c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 24 Jun 2014 15:57:24 -0700 Subject: [PATCH 169/488] Multithreaded build --- mp_compile.py | 50 ++++++++++++++++++++++++++++++++++++ setup.py | 71 +++++++++++++++++++++++++++------------------------ 2 files changed, 87 insertions(+), 34 deletions(-) create mode 100644 mp_compile.py diff --git a/mp_compile.py b/mp_compile.py new file mode 100644 index 000000000..0ff5b4b62 --- /dev/null +++ b/mp_compile.py @@ -0,0 +1,50 @@ +# A monkey patch of the base distutils.ccompiler to use parallel builds +# Tested on 2.7, looks to be identical to 3.3. + +from multiprocessing import Pool, cpu_count +from distutils.ccompiler import CCompiler +import os + +# hideous monkeypatching. but. but. but. +def _mp_compile_one(tp): + (self, obj, build, cc_args, extra_postargs, pp_opts) = tp + try: + src, ext = build[obj] + except KeyError: + return + self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) + return + +def _mp_compile(self, sources, output_dir=None, macros=None, + include_dirs=None, debug=0, extra_preargs=None, + extra_postargs=None, depends=None): + """Compile one or more source files. + + see distutils.ccompiler.CCompiler.compile for comments. + """ + # A concrete compiler class can either override this method + # entirely or implement _compile(). + + macros, objects, extra_postargs, pp_opts, build = \ + self._setup_compile(output_dir, macros, include_dirs, sources, + depends, extra_postargs) + cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) + + + try: + max_procs = int(os.environ.get('MAX_CONCURRENCY', cpu_count())) + except: + max_procs = None + pool = Pool(max_procs) + try: + print ("Building using %d processes" % pool._processes) + except: pass + arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects] + results = pool.map_async(_mp_compile_one,arr) + + pool.close() + pool.join() + # Return *all* object filenames, not just the ones we just built. + return objects + +CCompiler.compile = _mp_compile diff --git a/setup.py b/setup.py index 2fbcc2959..1a31a8981 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,8 @@ import re import struct import sys +import mp_compile + from distutils.command.build_ext import build_ext from distutils import sysconfig from setuptools import Extension, setup, find_packages @@ -705,39 +707,40 @@ class pil_build_ext(build_ext): finally: os.unlink(tmpfile) -setup( - name=NAME, - version=VERSION, - description='Python Imaging Library (Fork)', - long_description=( - _read('README.rst') + b'\n' + - _read('CHANGES.rst')).decode('utf-8'), - author='Alex Clark (fork author)', - author_email='aclark@aclark.net', - url='http://python-pillow.github.io/', - classifiers=[ - "Development Status :: 6 - Mature", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Scanners", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", ], - cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], - include_package_data=True, - packages=find_packages(), - scripts=glob.glob("Scripts/pil*.py"), - test_suite='PIL.tests', - keywords=["Imaging", ], - license='Standard PIL License', - zip_safe=True, +if __name__=='__main__': + setup( + name=NAME, + version=VERSION, + description='Python Imaging Library (Fork)', + long_description=( + _read('README.rst') + b'\n' + + _read('CHANGES.rst')).decode('utf-8'), + author='Alex Clark (fork author)', + author_email='aclark@aclark.net', + url='http://python-pillow.github.io/', + classifiers=[ + "Development Status :: 6 - Mature", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", + "Topic :: Multimedia :: Graphics :: Capture :: Scanners", + "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + "Topic :: Multimedia :: Graphics :: Viewers", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", ], + cmdclass={"build_ext": pil_build_ext}, + ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + include_package_data=True, + packages=find_packages(), + scripts=glob.glob("Scripts/pil*.py"), + test_suite='PIL.tests', + keywords=["Imaging", ], + license='Standard PIL License', + zip_safe=True, ) - + # End of file From ff95b8bc513d75926eeffc800465cd2402ec48d9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 24 Jun 2014 16:01:05 -0700 Subject: [PATCH 170/488] Limit buildprocs on travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 389051358..4de1fde99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: python notifications: irc: "chat.freenode.net#pil" +env: MAX_CONCURRENCY=4 + python: - "pypy" - 2.6 From 1be36946c17ae058e7d4d7b5cd2adccffad45b1c Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 24 Jun 2014 19:06:05 -0700 Subject: [PATCH 171/488] Fix segfault when importing _imagingmorph --- _imagingmorph.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/_imagingmorph.c b/_imagingmorph.c index 7e7fdd879..1dee7eb64 100644 --- a/_imagingmorph.c +++ b/_imagingmorph.c @@ -267,9 +267,10 @@ setup_module(PyObject* m) static PyMethodDef functions[] = { /* Functions */ - {"apply", (PyCFunction)apply, 1}, - {"get_on_pixels", (PyCFunction)get_on_pixels, 1}, - {"match", (PyCFunction)match, 1}, + {"apply", (PyCFunction)apply, METH_VARARGS, NULL}, + {"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL}, + {"match", (PyCFunction)match, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} }; #if PY_VERSION_HEX >= 0x03000000 From b593ff06adbcfbe713f637dea220f8884a6bd83e Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 24 Jun 2014 22:23:17 -0700 Subject: [PATCH 172/488] Fix msvc warning: 'inline' : macro redefinition --- libImaging/ImPlatform.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libImaging/ImPlatform.h b/libImaging/ImPlatform.h index 92c126a10..8e85e627c 100644 --- a/libImaging/ImPlatform.h +++ b/libImaging/ImPlatform.h @@ -17,12 +17,12 @@ #error Sorry, this library requires ANSI header files. #endif +#if !defined(PIL_USE_INLINE) +#define inline +#else #if defined(_MSC_VER) && !defined(__GNUC__) #define inline __inline #endif - -#if !defined(PIL_USE_INLINE) -#define inline #endif #ifdef _WIN32 From a5ae40c1b43ab97df21ff3655caebf5e66c8813b Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 24 Jun 2014 22:53:23 -0700 Subject: [PATCH 173/488] Fix AttributeError: class Image has no attribute 'DEBUG' --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 0d97e9dc7..787e60270 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -539,7 +539,7 @@ class Image: try: self.fp.close() except Exception as msg: - if Image.DEBUG: + if DEBUG: print ("Error closing: %s" % msg) # Instead of simply setting to None, we're setting up a From e79f996a45d3a6927735ad5e5c5b581378194ea2 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 25 Jun 2014 00:07:07 -0700 Subject: [PATCH 174/488] Fix test_imagedraw failures --- libImaging/Imaging.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 26207d121..d958387c9 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -20,7 +20,7 @@ extern "C" { #ifndef M_PI -#define M_PI 3.14159265359 +#define M_PI 3.1415926535897932384626433832795 #endif From f41e0a30fbeaabd16642e924415ceede8da8e59c Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:10:20 +0300 Subject: [PATCH 175/488] More tests cleanup --- PIL/tests.py | 17 ----------------- Tests/helper.py | 2 ++ 2 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 PIL/tests.py diff --git a/PIL/tests.py b/PIL/tests.py deleted file mode 100644 index eb4a8342d..000000000 --- a/PIL/tests.py +++ /dev/null @@ -1,17 +0,0 @@ -import unittest - - -class PillowTests(unittest.TestCase): - """ - Can we start moving the test suite here? - """ - - def test_suite_should_move_here(self): - """ - Great idea! - """ - assert True is True - - -if __name__ == '__main__': - unittest.main() diff --git a/Tests/helper.py b/Tests/helper.py index ebd142bf7..051912897 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -10,6 +10,8 @@ else: import unittest +# This should be imported into every test_XXX.py file to report +# any remaining temp files at the end of the run. def tearDownModule(): import glob import os From 1bbe850f5b9b363ea90c358ed0d691d30e334480 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:19:27 +0300 Subject: [PATCH 176/488] Convert bench_cffi_access.py to use unittest and helper.py --- Tests/bench_cffi_access.py | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 8f8ef937a..8aa322aff 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -1,23 +1,25 @@ -from tester import * +from helper import * -# not running this test by default. No DOS against travis. +# Not running this test by default. No DOS against Travis CI. from PIL import PyAccess -from PIL import Image import time + def iterate_get(size, access): - (w,h) = size + (w, h) = size for x in range(w): for y in range(h): - access[(x,y)] + access[(x, y)] + def iterate_set(size, access): - (w,h) = size + (w, h) = size for x in range(w): for y in range(h): - access[(x,y)] = (x %256,y%256,0) + access[(x, y)] = (x % 256, y % 256, 0) + def timer(func, label, *args): iterations = 5000 @@ -25,27 +27,34 @@ def timer(func, label, *args): for x in range(iterations): func(*args) if time.time()-starttime > 10: - print ("%s: breaking at %s iterations, %.6f per iteration"%(label, x+1, (time.time()-starttime)/(x+1.0))) + print("%s: breaking at %s iterations, %.6f per iteration" % ( + label, x+1, (time.time()-starttime)/(x+1.0))) break if x == iterations-1: endtime = time.time() - print ("%s: %.4f s %.6f per iteration" %(label, endtime-starttime, (endtime-starttime)/(x+1.0))) + print("%s: %.4f s %.6f per iteration" % ( + label, endtime-starttime, (endtime-starttime)/(x+1.0))) -def test_direct(): - im = lena() - im.load() - #im = Image.new( "RGB", (2000,2000), (1,3,2)) - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) - assert_equal(caccess[(0,0)], access[(0,0)]) +class BenchCffiAccess(PillowTestCase): - print ("Size: %sx%s" % im.size) - timer(iterate_get, 'PyAccess - get', im.size, access) - timer(iterate_set, 'PyAccess - set', im.size, access) - timer(iterate_get, 'C-api - get', im.size, caccess) - timer(iterate_set, 'C-api - set', im.size, caccess) - - + def test_direct(self): + im = lena() + im.load() + # im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) - + self.assertEqual(caccess[(0, 0)], access[(0, 0)]) + + print ("Size: %sx%s" % im.size) + timer(iterate_get, 'PyAccess - get', im.size, access) + timer(iterate_set, 'PyAccess - set', im.size, access) + timer(iterate_get, 'C-api - get', im.size, caccess) + timer(iterate_set, 'C-api - set', im.size, caccess) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 3b3fc441b125bdcf8c57b786fbb61243ea3260c4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:25:51 +0300 Subject: [PATCH 177/488] flake8 Tests/make_hash.py --- Tests/make_hash.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/make_hash.py b/Tests/make_hash.py index 71e208cff..32196e9f8 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -1,7 +1,5 @@ # brute-force search for access descriptor hash table -import random - modes = [ "1", "L", "LA", @@ -13,12 +11,14 @@ modes = [ "YCbCr", ] + def hash(s, i): # djb2 hash: multiply by 33 and xor character for c in s: - i = (((i<<5) + i) ^ ord(c)) & 0xffffffff + i = (((i << 5) + i) ^ ord(c)) & 0xffffffff return i + def check(size, i0): h = [None] * size for m in modes: From c6386a294da5de009b379820bd9416107678c8cb Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:32:52 +0300 Subject: [PATCH 178/488] Convert large_memory_test.py to use unittest (and change second XDIM on line 30 to YDIM) --- Tests/large_memory_test.py | 41 +++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 148841ec2..57ef67b1c 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -1,4 +1,4 @@ -from tester import * +from helper import * # This test is not run automatically. # @@ -6,22 +6,31 @@ from tester import * # second test. Running this automatically would amount to a denial of # service on our testing infrastructure. I expect this test to fail # on any 32 bit machine, as well as any smallish things (like -# raspberrypis). It does succeed on a 3gb Ubuntu 12.04x64 VM on python -# 2.7 an 3.2 +# Raspberry Pis). It does succeed on a 3gb Ubuntu 12.04x64 VM on Python +# 2.7 an 3.2. from PIL import Image -ydim = 32769 -xdim = 48000 -f = tempfile('temp.png') +YDIM = 32769 +XDIM = 48000 -def _write_png(xdim,ydim): - im = Image.new('L',(xdim,ydim),(0)) - im.save(f) - success() -def test_large(): - """ succeeded prepatch""" - _write_png(xdim,ydim) -def test_2gpx(): - """failed prepatch""" - _write_png(xdim,xdim) +class TestImage(PillowTestCase): + + def _write_png(self, XDIM, YDIM): + f = self.tempfile('temp.png') + im = Image.new('L', (XDIM, YDIM), (0)) + im.save(f) + + def test_large(self): + """ succeeded prepatch""" + self._write_png(XDIM, YDIM) + + def test_2gpx(self): + """failed prepatch""" + self._write_png(XDIM, XDIM) + + +if __name__ == '__main__': + unittest.main() + +# End of file From cf07aa60a18951cf59f373c74823b54a14c2965f Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:43:01 +0300 Subject: [PATCH 179/488] Convert large_memory_numpy_test.py to use unittest (and change second XDIM on line 36 to YDIM) --- Tests/large_memory_numpy_test.py | 45 ++++++++++++++++++-------------- Tests/large_memory_test.py | 4 +-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index eb9b8aa01..17ccd3de2 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -1,4 +1,4 @@ -from tester import * +from helper import * # This test is not run automatically. # @@ -6,32 +6,37 @@ from tester import * # second test. Running this automatically would amount to a denial of # service on our testing infrastructure. I expect this test to fail # on any 32 bit machine, as well as any smallish things (like -# raspberrypis). +# Raspberry Pis). from PIL import Image try: import numpy as np except: - skip() - -ydim = 32769 -xdim = 48000 -f = tempfile('temp.png') + sys.exit("Skipping: Numpy not installed") -def _write_png(xdim,ydim): - dtype = np.uint8 - a = np.zeros((xdim, ydim), dtype=dtype) - im = Image.fromarray(a, 'L') - im.save(f) - success() - -def test_large(): - """ succeeded prepatch""" - _write_png(xdim,ydim) -def test_2gpx(): - """failed prepatch""" - _write_png(xdim,xdim) +YDIM = 32769 +XDIM = 48000 +class LargeMemoryNumpyTest(PillowTestCase): + + def _write_png(self, XDIM, YDIM): + dtype = np.uint8 + a = np.zeros((XDIM, YDIM), dtype=dtype) + f = self.tempfile('temp.png') + im = Image.fromarray(a, 'L') + im.save(f) + + def test_large(self): + """ succeeded prepatch""" + self._write_png(XDIM, YDIM) + + def test_2gpx(self): + """failed prepatch""" + self._write_png(XDIM, YDIM) +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 57ef67b1c..545e284f4 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -14,7 +14,7 @@ YDIM = 32769 XDIM = 48000 -class TestImage(PillowTestCase): +class LargeMemoryTest(PillowTestCase): def _write_png(self, XDIM, YDIM): f = self.tempfile('temp.png') @@ -27,7 +27,7 @@ class TestImage(PillowTestCase): def test_2gpx(self): """failed prepatch""" - self._write_png(XDIM, XDIM) + self._write_png(XDIM, YDIM) if __name__ == '__main__': From 5993b97cb04d28114db63cc15cc883ba6580561b Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 12:46:52 +0300 Subject: [PATCH 180/488] Remove last dependencies on tester.py and remove file --- Tests/bench_get.py | 7 +- Tests/run.py | 135 ---------------- Tests/tester.py | 388 --------------------------------------------- 3 files changed, 4 insertions(+), 526 deletions(-) delete mode 100644 Tests/run.py delete mode 100644 Tests/tester.py diff --git a/Tests/bench_get.py b/Tests/bench_get.py index eca491600..8a1331d39 100644 --- a/Tests/bench_get.py +++ b/Tests/bench_get.py @@ -1,13 +1,14 @@ import sys sys.path.insert(0, ".") -import tester +import helper import timeit + def bench(mode): - im = tester.lena(mode) + im = helper.lena(mode) get = im.im.getpixel - xy = 50, 50 # position shouldn't really matter + xy = 50, 50 # position shouldn't really matter t0 = timeit.default_timer() for i in range(1000000): get(xy) diff --git a/Tests/run.py b/Tests/run.py deleted file mode 100644 index 82e1b94dc..000000000 --- a/Tests/run.py +++ /dev/null @@ -1,135 +0,0 @@ -from __future__ import print_function - -# minimal test runner - -import glob -import os -import os.path -import re -import sys -import tempfile - -try: - root = os.path.dirname(__file__) -except NameError: - root = os.path.dirname(sys.argv[0]) - -if not os.path.isfile("PIL/Image.py"): - print("***", "please run this script from the PIL development directory as") - print("***", "$ python Tests/run.py") - sys.exit(1) - -print("-"*68) - -python_options = [] -tester_options = [] - -if "--installed" not in sys.argv: - os.environ["PYTHONPATH"] = "." - -if "--coverage" in sys.argv: - tester_options.append("--coverage") - -if "--log" in sys.argv: - tester_options.append("--log") - -files = glob.glob(os.path.join(root, "test_*.py")) -files.sort() - -success = failure = 0 -include = [x for x in sys.argv[1:] if x[:2] != "--"] -skipped = [] -failed = [] - -python_options = " ".join(python_options) -tester_options = " ".join(tester_options) - -ignore_re = re.compile('^ignore: (.*)$', re.MULTILINE) - -for file in files: - test, ext = os.path.splitext(os.path.basename(file)) - if include and test not in include: - continue - print("running", test, "...") - # 2>&1 works on unix and on modern windowses. we might care about - # very old Python versions, but not ancient microsoft products :-) - out = os.popen("%s %s -u %s %s 2>&1" % ( - sys.executable, python_options, file, tester_options - )) - result = out.read() - - result_lines = result.splitlines() - if len(result_lines): - if result_lines[0] == "ignore_all_except_last_line": - result = result_lines[-1] - - # Extract any ignore patterns - ignore_pats = ignore_re.findall(result) - result = ignore_re.sub('', result) - - try: - def fix_re(p): - if not p.startswith('^'): - p = '^' + p - if not p.endswith('$'): - p += '$' - return p - - ignore_res = [re.compile(fix_re(p), re.MULTILINE) for p in ignore_pats] - except: - print('(bad ignore patterns %r)' % ignore_pats) - ignore_res = [] - - for r in ignore_res: - result = r.sub('', result) - - result = result.strip() - - if result == "ok": - result = None - elif result == "skip": - print("---", "skipped") # FIXME: driver should include a reason - skipped.append(test) - continue - elif not result: - result = "(no output)" - status = out.close() - if status or result: - if status: - print("=== error", status) - if result: - if result[-3:] == "\nok": - # if there's an ok at the end, it's not really ok - result = result[:-3] - print(result) - failed.append(test) - else: - success += 1 - -print("-"*68) - -temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') -tempfiles = glob.glob(os.path.join(temp_root, "temp_*")) -if tempfiles: - print("===", "remaining temporary files") - for file in tempfiles: - print(file) - print("-"*68) - - -def tests(n): - if n == 1: - return "1 test" - else: - return "%d tests" % n - -if skipped: - print("---", tests(len(skipped)), "skipped:") - print(", ".join(skipped)) -if failed: - failure = len(failed) - print("***", tests(failure), "of", (success + failure), "failed:") - print(", ".join(failed)) - sys.exit(1) -else: - print(tests(success), "passed.") diff --git a/Tests/tester.py b/Tests/tester.py deleted file mode 100644 index 866009251..000000000 --- a/Tests/tester.py +++ /dev/null @@ -1,388 +0,0 @@ -from __future__ import print_function - -# require that deprecation warnings are triggered -import warnings -warnings.simplefilter('default') -# temporarily turn off resource warnings that warn about unclosed -# files in the test scripts. -try: - warnings.filterwarnings("ignore", category=ResourceWarning) -except NameError: - # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. - pass - -import sys -py3 = (sys.version_info >= (3, 0)) - -# some test helpers - -_target = None -_tempfiles = [] -_logfile = None - - -def success(): - import sys - success.count += 1 - if _logfile: - print(sys.argv[0], success.count, failure.count, file=_logfile) - return True - - -def failure(msg=None, frame=None): - import sys - import linecache - failure.count += 1 - if _target: - if frame is None: - frame = sys._getframe() - while frame.f_globals.get("__name__") != _target.__name__: - frame = frame.f_back - location = (frame.f_code.co_filename, frame.f_lineno) - prefix = "%s:%d: " % location - line = linecache.getline(*location) - print(prefix + line.strip() + " failed:") - if msg: - print("- " + msg) - if _logfile: - print(sys.argv[0], success.count, failure.count, file=_logfile) - return False - -success.count = failure.count = 0 - - -# predicates - -def assert_true(v, msg=None): - if v: - success() - else: - failure(msg or "got %r, expected true value" % v) - - -def assert_false(v, msg=None): - if v: - failure(msg or "got %r, expected false value" % v) - else: - success() - - -def assert_equal(a, b, msg=None): - if a == b: - success() - else: - failure(msg or "got %r, expected %r" % (a, b)) - - -def assert_almost_equal(a, b, msg=None, eps=1e-6): - if abs(a-b) < eps: - success() - else: - failure(msg or "got %r, expected %r" % (a, b)) - - -def assert_deep_equal(a, b, msg=None): - try: - if len(a) == len(b): - if all([x == y for x, y in zip(a, b)]): - success() - else: - failure(msg or "got %s, expected %s" % (a, b)) - else: - failure(msg or "got length %s, expected %s" % (len(a), len(b))) - except: - assert_equal(a, b, msg) - - -def assert_greater(a, b, msg=None): - if a > b: - success() - else: - failure(msg or "%r unexpectedly not greater than %r" % (a, b)) - - -def assert_greater_equal(a, b, msg=None): - if a >= b: - success() - else: - failure( - msg or "%r unexpectedly not greater than or equal to %r" % (a, b)) - - -def assert_less(a, b, msg=None): - if a < b: - success() - else: - failure(msg or "%r unexpectedly not less than %r" % (a, b)) - - -def assert_less_equal(a, b, msg=None): - if a <= b: - success() - else: - failure( - msg or "%r unexpectedly not less than or equal to %r" % (a, b)) - - -def assert_is_instance(a, b, msg=None): - if isinstance(a, b): - success() - else: - failure(msg or "got %r, expected %r" % (type(a), b)) - - -def assert_in(a, b, msg=None): - if a in b: - success() - else: - failure(msg or "%r unexpectedly not in %r" % (a, b)) - - -def assert_match(v, pattern, msg=None): - import re - if re.match(pattern, v): - success() - else: - failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) - - -def assert_exception(exc_class, func): - import sys - import traceback - try: - func() - except exc_class: - success() - except: - failure("expected %r exception, got %r" % ( - exc_class.__name__, sys.exc_info()[0].__name__)) - traceback.print_exc() - else: - failure("expected %r exception, got no exception" % exc_class.__name__) - - -def assert_no_exception(func): - import sys - import traceback - try: - func() - except: - failure("expected no exception, got %r" % sys.exc_info()[0].__name__) - traceback.print_exc() - else: - success() - - -def assert_warning(warn_class, func): - # note: this assert calls func three times! - import warnings - - def warn_error(message, category=UserWarning, **options): - raise category(message) - - def warn_ignore(message, category=UserWarning, **options): - pass - warn = warnings.warn - result = None - try: - warnings.warn = warn_ignore - assert_no_exception(func) - result = func() - warnings.warn = warn_error - assert_exception(warn_class, func) - finally: - warnings.warn = warn # restore - return result - -# helpers - -from io import BytesIO - - -def fromstring(data): - from PIL import Image - return Image.open(BytesIO(data)) - - -def tostring(im, format, **options): - out = BytesIO() - im.save(out, format, **options) - return out.getvalue() - - -def lena(mode="RGB", cache={}): - from PIL import Image - im = cache.get(mode) - if im is None: - if mode == "RGB": - im = Image.open("Tests/images/lena.ppm") - elif mode == "F": - im = lena("L").convert(mode) - elif mode[:4] == "I;16": - im = lena("I").convert(mode) - else: - im = lena("RGB").convert(mode) - cache[mode] = im - return im - - -def assert_image(im, mode, size, msg=None): - if mode is not None and im.mode != mode: - failure(msg or "got mode %r, expected %r" % (im.mode, mode)) - elif size is not None and im.size != size: - failure(msg or "got size %r, expected %r" % (im.size, size)) - else: - success() - - -def assert_image_equal(a, b, msg=None): - if a.mode != b.mode: - failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) - elif a.size != b.size: - failure(msg or "got size %r, expected %r" % (a.size, b.size)) - elif a.tobytes() != b.tobytes(): - failure(msg or "got different content") - else: - success() - - -def assert_image_completely_equal(a, b, msg=None): - if a != b: - failure(msg or "images different") - else: - success() - - -def assert_image_similar(a, b, epsilon, msg=None): - epsilon = float(epsilon) - if a.mode != b.mode: - return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) - elif a.size != b.size: - return failure(msg or "got size %r, expected %r" % (a.size, b.size)) - diff = 0 - try: - ord(b'0') - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(ord(abyte)-ord(bbyte)) - except: - for abyte, bbyte in zip(a.tobytes(), b.tobytes()): - diff += abs(abyte-bbyte) - ave_diff = float(diff)/(a.size[0]*a.size[1]) - if epsilon < ave_diff: - return failure( - msg or "average pixel value difference %.4f > epsilon %.4f" % ( - ave_diff, epsilon)) - else: - return success() - - -def tempfile(template, *extra): - import os - import os.path - import sys - import tempfile - files = [] - root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.mkdir(root) - except OSError: - pass - for temp in (template,) + extra: - assert temp[:5] in ("temp.", "temp_") - name = os.path.basename(sys.argv[0]) - name = temp[:4] + os.path.splitext(name)[0][4:] - name = name + "_%d" % len(_tempfiles) + temp[4:] - name = os.path.join(root, name) - files.append(name) - _tempfiles.extend(files) - return files[0] - - -# test runner - -def run(): - global _target, _tests, run - import sys - import traceback - _target = sys.modules["__main__"] - run = None # no need to run twice - tests = [] - for name, value in list(vars(_target).items()): - if name[:5] == "test_" and type(value) is type(success): - tests.append((value.__code__.co_firstlineno, name, value)) - tests.sort() # sort by line - for lineno, name, func in tests: - try: - _tests = [] - func() - for func, args in _tests: - func(*args) - except: - t, v, tb = sys.exc_info() - tb = tb.tb_next - if tb: - failure(frame=tb.tb_frame) - traceback.print_exception(t, v, tb) - else: - print("%s:%d: cannot call test function: %s" % ( - sys.argv[0], lineno, v)) - failure.count += 1 - - -def yield_test(function, *args): - # collect delayed/generated tests - _tests.append((function, args)) - - -def skip(msg=None): - import os - print("skip") - os._exit(0) # don't run exit handlers - - -def ignore(pattern): - """Tells the driver to ignore messages matching the pattern, for the - duration of the current test.""" - print('ignore: %s' % pattern) - - -def _setup(): - global _logfile - - import sys - if "--coverage" in sys.argv: - # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - import coverage - cov = coverage.coverage(auto_data=True, include="PIL/*") - cov.start() - - def report(): - if run: - run() - if success.count and not failure.count: - print("ok") - # only clean out tempfiles if test passed - import os - import os.path - import tempfile - for file in _tempfiles: - try: - os.remove(file) - except OSError: - pass # report? - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.rmdir(temp_root) - except OSError: - pass - - import atexit - atexit.register(report) - - if "--log" in sys.argv: - _logfile = open("test.log", "a") - - -_setup() From 433ec1c219fe94d2431e1dbb9a55fc3362b39d64 Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 25 Jun 2014 11:13:33 -0400 Subject: [PATCH 181/488] Clean commit of 16-bit monochrome JPEK2000 support --- PIL/Jpeg2KImagePlugin.py | 24 +++++++++++++----- libImaging/Jpeg2KDecode.c | 51 +++++++++++++++++++++++++++++++++++++++ libImaging/Jpeg2KEncode.c | 33 ++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index c4c980f6e..c75b38576 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -40,7 +40,10 @@ def _parse_codestream(fp): size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - mode = 'L' + if len(yrsiz) > 0 and yrsiz[0] > 8: + mode = 'I' + else: + mode = 'L' elif csiz == 2: mode = 'LA' elif csiz == 3: @@ -78,6 +81,7 @@ def _parse_jp2_header(fp): size = None mode = None + bpc = None hio = io.BytesIO(header) while True: @@ -95,7 +99,9 @@ def _parse_jp2_header(fp): = struct.unpack('>IIHBBBB', content) size = (width, height) if unkc: - if nc == 1: + if nc == 1 and bpc > 8: + mode = 'I' + elif nc == 1: mode = 'L' elif nc == 2: mode = 'LA' @@ -109,13 +115,19 @@ def _parse_jp2_header(fp): if meth == 1: cs = struct.unpack('>I', content[3:7])[0] if cs == 16: # sRGB - if nc == 3: + if nc == 1 and bpc > 8: + mode = 'I' + elif nc == 1: + mode = 'L' + elif nc == 3: mode = 'RGB' elif nc == 4: mode = 'RGBA' break elif cs == 17: # grayscale - if nc == 1: + if nc == 1 and bpc > 8: + mode = 'I' + elif nc == 1: mode = 'L' elif nc == 2: mode = 'LA' @@ -129,10 +141,10 @@ def _parse_jp2_header(fp): return (size, mode) - ## # Image plugin for JPEG2000 images. + class Jpeg2KImageFile(ImageFile.ImageFile): format = "JPEG2000" format_description = "JPEG 2000 (ISO 15444)" @@ -174,7 +186,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): f.seek(pos, 0) except: length = -1 - + self.tile = [('jpeg2k', (0, 0) + self.size, 0, (self.codec, self.reduce, self.layers, fd, length))] diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 1b61b4f7d..76c1d169b 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -135,6 +135,56 @@ j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } } + +static void +j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, Imaging im) +{ + unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; + unsigned w = tileinfo->x1 - tileinfo->x0; + unsigned h = tileinfo->y1 - tileinfo->y0; + + int shift = 16 - in->comps[0].prec; + int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0; + int csiz = (in->comps[0].prec + 7) >> 3; + + unsigned x, y; + + if (csiz == 3) + csiz = 4; + + if (shift < 0) + offset += 1 << (-shift - 1); + + switch (csiz) { + case 1: + for (y = 0; y < h; ++y) { + const UINT8 *data = &tiledata[y * w]; + UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } + break; + case 2: + for (y = 0; y < h; ++y) { + const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; + UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } + break; + case 4: + for (y = 0; y < h; ++y) { + const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; + UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) + *row++ = j2ku_shift(offset + *data++, shift); + } + break; + } +} + + static void j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im) @@ -466,6 +516,7 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, static const struct j2k_decode_unpacker j2k_unpackers[] = { { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, + { "I", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index 8e7d0d1f2..8d8737474 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -88,6 +88,22 @@ j2k_pack_l(Imaging im, UINT8 *buf, } } +static void +j2k_pack_i16(Imaging im, UINT8 *buf, + unsigned x0, unsigned y0, unsigned w, unsigned h) +{ + UINT8 *ptr = buf; + unsigned x,y; + for (y = 0; y < h; ++y) { + UINT8 *data = (UINT8 *)(im->image[y + y0] + x0); + for (x = 0; x < w; ++x) { + *ptr++ = *data++; + *ptr++ = *data++; + } + } +} + + static void j2k_pack_la(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) @@ -247,6 +263,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, j2k_pack_tile_t pack; int ret = -1; + unsigned prec = 8; + unsigned bpp = 8; + stream = opj_stream_default_create(OPJ_FALSE); if (!stream) { @@ -271,6 +290,12 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; + } else if (strcmp (im->mode, "I")){ + components = 1; + color_space = OPJ_CLRSPC_GRAY; + pack = j2k_pack_i16; + prec = 16; + bpp = 12; } else if (strcmp (im->mode, "LA") == 0) { components = 2; color_space = OPJ_CLRSPC_GRAY; @@ -298,8 +323,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, image_params[n].w = im->xsize; image_params[n].h = im->ysize; image_params[n].x0 = image_params[n].y0 = 0; - image_params[n].prec = 8; - image_params[n].bpp = 8; + image_params[n].prec = prec; + image_params[n].bpp = bpp; image_params[n].sgnd = 0; } @@ -442,7 +467,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, num_tiles = tiles_x * tiles_y; - state->buffer = malloc (tile_width * tile_height * components); + state->buffer = malloc (tile_width * tile_height * components * prec / 8); tile_ndx = 0; for (y = 0; y < tiles_y; ++y) { @@ -474,7 +499,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, pack(im, state->buffer, pixx, pixy, pixw, pixh); - data_size = pixw * pixh * components; + data_size = pixw * pixh * components * prec / 8; if (!opj_write_tile(codec, tile_ndx++, state->buffer, data_size, stream)) { From 479693417fe2de2ac248fb9f02375e793721d8c8 Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 25 Jun 2014 11:42:06 -0400 Subject: [PATCH 182/488] Merge the rest of the patches Now it actually works and passes the test suite --- PIL/Jpeg2KImagePlugin.py | 8 ++++---- libImaging/Jpeg2KEncode.c | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index c75b38576..ff6ca4b35 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -40,7 +40,7 @@ def _parse_codestream(fp): size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - if len(yrsiz) > 0 and yrsiz[0] > 8: + if (ssiz[0] & 0x7f) > 8: mode = 'I' else: mode = 'L' @@ -99,7 +99,7 @@ def _parse_jp2_header(fp): = struct.unpack('>IIHBBBB', content) size = (width, height) if unkc: - if nc == 1 and bpc > 8: + if nc == 1 and (bpc & 0x7f) > 8: mode = 'I' elif nc == 1: mode = 'L' @@ -115,7 +115,7 @@ def _parse_jp2_header(fp): if meth == 1: cs = struct.unpack('>I', content[3:7])[0] if cs == 16: # sRGB - if nc == 1 and bpc > 8: + if nc == 1 and (bpc & 0x7f) > 8: mode = 'I' elif nc == 1: mode = 'L' @@ -125,7 +125,7 @@ def _parse_jp2_header(fp): mode = 'RGBA' break elif cs == 17: # grayscale - if nc == 1 and bpc > 8: + if nc == 1 and (bpc & 0x7f) > 8: mode = 'I' elif nc == 1: mode = 'L' diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c index 8d8737474..e8eef08c1 100644 --- a/libImaging/Jpeg2KEncode.c +++ b/libImaging/Jpeg2KEncode.c @@ -290,7 +290,13 @@ j2k_encode_entry(Imaging im, ImagingCodecState state, components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; - } else if (strcmp (im->mode, "I")){ + } else if (strcmp (im->mode, "I;16") == 0){ + components = 1; + color_space = OPJ_CLRSPC_GRAY; + pack = j2k_pack_i16; + prec = 16; + bpp = 12; + } else if (strcmp (im->mode, "I;16B") == 0){ components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_i16; From d391390e3f14bf897e048ed2388468d42621efdf Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 25 Jun 2014 19:27:43 +0300 Subject: [PATCH 183/488] Second test is meant to have two XDIMs (#728). Also use lowercase for parameters. --- Tests/large_memory_numpy_test.py | 6 +++--- Tests/large_memory_test.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index 17ccd3de2..deb2fc8c6 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -20,9 +20,9 @@ XDIM = 48000 class LargeMemoryNumpyTest(PillowTestCase): - def _write_png(self, XDIM, YDIM): + def _write_png(self, xdim, ydim): dtype = np.uint8 - a = np.zeros((XDIM, YDIM), dtype=dtype) + a = np.zeros((xdim, ydim), dtype=dtype) f = self.tempfile('temp.png') im = Image.fromarray(a, 'L') im.save(f) @@ -33,7 +33,7 @@ class LargeMemoryNumpyTest(PillowTestCase): def test_2gpx(self): """failed prepatch""" - self._write_png(XDIM, YDIM) + self._write_png(XDIM, XDIM) if __name__ == '__main__': diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 545e284f4..8552ed4dd 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -16,9 +16,9 @@ XDIM = 48000 class LargeMemoryTest(PillowTestCase): - def _write_png(self, XDIM, YDIM): + def _write_png(self, xdim, ydim): f = self.tempfile('temp.png') - im = Image.new('L', (XDIM, YDIM), (0)) + im = Image.new('L', (xdim, ydim), (0)) im.save(f) def test_large(self): @@ -27,7 +27,7 @@ class LargeMemoryTest(PillowTestCase): def test_2gpx(self): """failed prepatch""" - self._write_png(XDIM, YDIM) + self._write_png(XDIM, XDIM) if __name__ == '__main__': From b147dea535cdb05b4692f4d58ec1ca585ad09d8e Mon Sep 17 00:00:00 2001 From: David Joy Date: Wed, 25 Jun 2014 14:06:56 -0400 Subject: [PATCH 184/488] Add tests and fix a 16bit vs 32bit integer bug Yay unit tests! --- PIL/Jpeg2KImagePlugin.py | 10 ++++----- Tests/images/16bit.cropped.j2k | Bin 0 -> 3886 bytes Tests/images/16bit.cropped.jp2 | Bin 0 -> 3932 bytes Tests/test_file_jpeg2k.py | 36 +++++++++++++++++++++++++++++++++ libImaging/Jpeg2KDecode.c | 9 +++++---- 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 Tests/images/16bit.cropped.j2k create mode 100644 Tests/images/16bit.cropped.jp2 diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index ff6ca4b35..66069802e 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -40,8 +40,8 @@ def _parse_codestream(fp): size = (xsiz - xosiz, ysiz - yosiz) if csiz == 1: - if (ssiz[0] & 0x7f) > 8: - mode = 'I' + if (yrsiz[0] & 0x7f) > 8: + mode = 'I;16' else: mode = 'L' elif csiz == 2: @@ -100,7 +100,7 @@ def _parse_jp2_header(fp): size = (width, height) if unkc: if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I' + mode = 'I;16' elif nc == 1: mode = 'L' elif nc == 2: @@ -116,7 +116,7 @@ def _parse_jp2_header(fp): cs = struct.unpack('>I', content[3:7])[0] if cs == 16: # sRGB if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I' + mode = 'I;16' elif nc == 1: mode = 'L' elif nc == 3: @@ -126,7 +126,7 @@ def _parse_jp2_header(fp): break elif cs == 17: # grayscale if nc == 1 and (bpc & 0x7f) > 8: - mode = 'I' + mode = 'I;16' elif nc == 1: mode = 'L' elif nc == 2: diff --git a/Tests/images/16bit.cropped.j2k b/Tests/images/16bit.cropped.j2k new file mode 100644 index 0000000000000000000000000000000000000000..c12df0cd7712f493ff5e49b16ee3cabd4fbea889 GIT binary patch literal 3886 zcmV+}57F@dPybN>DF6Tf002M$002M$0000000000002M$002M$00000000000S^HI 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 literal 0 HcmV?d00001 diff --git a/Tests/images/16bit.cropped.jp2 b/Tests/images/16bit.cropped.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..0f6685a462f5f1f5abfaf2ce4d141baba4c0da54 GIT binary patch literal 3932 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b662124b4..b763687f7 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -120,6 +120,42 @@ class TestFileJpeg2k(PillowTestCase): self.assertEqual(j2k.mode, 'RGBA') self.assertEqual(jp2.mode, 'RGBA') + def test_16bit_monochrome_has_correct_mode(self): + + j2k = Image.open('Tests/images/16bit.cropped.j2k') + jp2 = Image.open('Tests/images/16bit.cropped.jp2') + + j2k.load() + jp2.load() + + self.assertEqual(j2k.mode, 'I;16') + self.assertEqual(jp2.mode, 'I;16') + + def test_16bit_monchrome_jp2_like_tiff(self): + + tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') + jp2 = Image.open('Tests/images/16bit.cropped.jp2') + self.assert_image_similar(jp2, tiff_16bit, 1e-3) + + def test_16bit_monchrome_j2k_like_tiff(self): + + tiff_16bit = Image.open('Tests/images/16bit.cropped.tif') + j2k = Image.open('Tests/images/16bit.cropped.j2k') + self.assert_image_similar(j2k, tiff_16bit, 1e-3) + + def test_16bit_j2k_roundtrips(self): + + j2k = Image.open('Tests/images/16bit.cropped.j2k') + im = self.roundtrip(j2k) + self.assert_image_equal(im, j2k) + + def test_16bit_jp2_roundtrips(self): + + jp2 = Image.open('Tests/images/16bit.cropped.jp2') + im = self.roundtrip(jp2) + self.assert_image_equal(im, jp2) + + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Jpeg2KDecode.c b/libImaging/Jpeg2KDecode.c index 76c1d169b..97ec81003 100644 --- a/libImaging/Jpeg2KDecode.c +++ b/libImaging/Jpeg2KDecode.c @@ -160,7 +160,7 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, case 1: for (y = 0; y < h; ++y) { const UINT8 *data = &tiledata[y * w]; - UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) *row++ = j2ku_shift(offset + *data++, shift); } @@ -168,7 +168,7 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, case 2: for (y = 0; y < h; ++y) { const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; - UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) *row++ = j2ku_shift(offset + *data++, shift); } @@ -176,7 +176,7 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, case 4: for (y = 0; y < h; ++y) { const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; - UINT32 *row = (UINT32 *)im->image[y0 + y] + x0; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) *row++ = j2ku_shift(offset + *data++, shift); } @@ -516,7 +516,8 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, static const struct j2k_decode_unpacker j2k_unpackers[] = { { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, - { "I", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, + { "I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, + { "I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, From 87aaa2ecd734d2edc6cfea7e51169c68d5d2233f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 25 Jun 2014 22:28:27 -0700 Subject: [PATCH 185/488] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 06f6e7090..60841e663 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- 16-bit monochrome support for JPEG2000 + [videan42] + - Fixed ImagePalette.save [brightpisces] From ea4bccf54416cc14b140ef5758044650764d031f Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 26 Jun 2014 08:34:04 -0400 Subject: [PATCH 186/488] No longer use Tests/run.py --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index dca36d4ed..a226ea602 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ pre: bin/python setup.py develop bin/python selftest.py - bin/python Tests/run.py check-manifest pyroma . viewdoc From 85d8d4bb8f664a64eb00fde379fdece3a5d85f1d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 26 Jun 2014 08:35:46 -0400 Subject: [PATCH 187/488] Fix manifest --- MANIFEST.in | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index c2358f76f..79a70dddd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include *.c include *.h include *.py include *.rst +include .coveragerc include .gitattributes include .travis.yml include Makefile @@ -28,28 +29,42 @@ recursive-include Sane CHANGES recursive-include Sane README recursive-include Scripts *.py recursive-include Scripts README +recursive-include Tests *.bdf recursive-include Tests *.bin recursive-include Tests *.bmp +recursive-include Tests *.doc recursive-include Tests *.eps +recursive-include Tests *.fli recursive-include Tests *.gif recursive-include Tests *.gnuplot recursive-include Tests *.html recursive-include Tests *.icm recursive-include Tests *.icns recursive-include Tests *.ico +recursive-include Tests *.j2k recursive-include Tests *.jp2 recursive-include Tests *.jpg +recursive-include Tests *.lut +recursive-include Tests *.pbm recursive-include Tests *.pcf recursive-include Tests *.pcx +recursive-include Tests *.pgm +recursive-include Tests *.pil recursive-include Tests *.png recursive-include Tests *.ppm +recursive-include Tests *.psd recursive-include Tests *.py +recursive-include Tests *.spider +recursive-include Tests *.tar recursive-include Tests *.tif recursive-include Tests *.tiff recursive-include Tests *.ttf recursive-include Tests *.txt +recursive-include Tests *.webp +recursive-include Tests *.xpm recursive-include Tk *.c recursive-include Tk *.txt +recursive-include Tk *.rst recursive-include depends *.sh recursive-include docs *.bat recursive-include docs *.gitignore @@ -64,3 +79,5 @@ recursive-include docs COPYING recursive-include docs LICENSE recursive-include libImaging *.c recursive-include libImaging *.h +recursive-include Sane *.rst +recursive-include Scripts *.rst From f6171f8bbe33fa0f9be3fad0e1520c5ead5d5d6d Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 26 Jun 2014 20:59:51 +0300 Subject: [PATCH 188/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 60841e663..3f4299f5e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,18 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Fix test_imagedraw failures #727 + [cgohlke] + +- Fix AttributeError: class Image has no attribute 'DEBUG' #726 + [cgohlke] + +- Fix msvc warning: 'inline' : macro redefinition #725 + [cgohlke] + +- Cleanup #654 + [dvska, hugovk, wiredfool] + - 16-bit monochrome support for JPEG2000 [videan42] From 44e91e668ddb22a454fe41a50f6518ebcbff05bf Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 27 Jun 2014 11:29:30 +0300 Subject: [PATCH 189/488] Remove old, unused test --- Tests/cms_test.py | 222 ---------------------------------------------- 1 file changed, 222 deletions(-) delete mode 100644 Tests/cms_test.py diff --git a/Tests/cms_test.py b/Tests/cms_test.py deleted file mode 100644 index 464c9c7ef..000000000 --- a/Tests/cms_test.py +++ /dev/null @@ -1,222 +0,0 @@ -# PyCMSTests.py -# Examples of how to use pyCMS, as well as tests to verify it works properly -# By Kevin Cazabon (kevin@cazabon.com) - -# Imports -import os -from PIL import Image -from PIL import ImageCms - -# import PyCMSError separately so we can catch it -PyCMSError = ImageCms.PyCMSError - -####################################################################### -# Configuration: -####################################################################### -# set this to the image you want to test with -IMAGE = "c:\\temp\\test.tif" - -# set this to where you want to save the output images -OUTPUTDIR = "c:\\temp\\" - -# set these to two different ICC profiles, one for input, one for output -# set the corresponding mode to the proper PIL mode for that profile -INPUT_PROFILE = "c:\\temp\\profiles\\sRGB.icm" -INMODE = "RGB" - -OUTPUT_PROFILE = "c:\\temp\\profiles\\genericRGB.icm" -OUTMODE = "RGB" - -PROOF_PROFILE = "c:\\temp\\profiles\\monitor.icm" - -# set to True to show() images, False to save them into OUTPUT_DIRECTORY -SHOW = False - -# Tests you can enable/disable -TEST_error_catching = True -TEST_profileToProfile = True -TEST_profileToProfile_inPlace = True -TEST_buildTransform = True -TEST_buildTransformFromOpenProfiles = True -TEST_buildProofTransform = True -TEST_getProfileInfo = True -TEST_misc = False - -####################################################################### -# helper functions -####################################################################### -def outputImage(im, funcName = None): - # save or display the image, depending on value of SHOW_IMAGES - if SHOW: - im.show() - else: - im.save(os.path.join(OUTPUTDIR, "%s.tif" %funcName)) - - -####################################################################### -# The tests themselves -####################################################################### - -if TEST_error_catching: - im = Image.open(IMAGE) - try: - #neither of these proifles exists (unless you make them), so we should - # get an error - imOut = ImageCms.profileToProfile(im, "missingProfile.icm", "cmyk.icm") - - except PyCMSError as reason: - print("We caught a PyCMSError: %s\n\n" %reason) - - print("error catching test completed successfully (if you see the message \ - above that we caught the error).") - -if TEST_profileToProfile: - # open the image file using the standard PIL function Image.open() - im = Image.open(IMAGE) - - # send the image, input/output profiles, and rendering intent to - # ImageCms.profileToProfile() - imOut = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \ - outputMode = OUTMODE) - - # now that the image is converted, save or display it - outputImage(imOut, "profileToProfile") - - print("profileToProfile test completed successfully.") - -if TEST_profileToProfile_inPlace: - # we'll do the same test as profileToProfile, but modify im in place - # instead of getting a new image returned to us - im = Image.open(IMAGE) - - # send the image to ImageCms.profileToProfile(), specifying inPlace = True - result = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \ - outputMode = OUTMODE, inPlace = True) - - # now that the image is converted, save or display it - if result is None: - # this is the normal result when modifying in-place - outputImage(im, "profileToProfile_inPlace") - else: - # something failed... - print("profileToProfile in-place failed: %s" %result) - - print("profileToProfile in-place test completed successfully.") - -if TEST_buildTransform: - # make a transform using the input and output profile path strings - transform = ImageCms.buildTransform(INPUT_PROFILE, OUTPUT_PROFILE, INMODE, \ - OUTMODE) - - # now, use the trnsform to convert a couple images - im = Image.open(IMAGE) - - # transform im normally - im2 = ImageCms.applyTransform(im, transform) - outputImage(im2, "buildTransform") - - # then transform it again using the same transform, this time in-place. - result = ImageCms.applyTransform(im, transform, inPlace = True) - outputImage(im, "buildTransform_inPlace") - - print("buildTransform test completed successfully.") - - # and, to clean up a bit, delete the transform - # this should call the C destructor for the transform structure. - # Python should also do this automatically when it goes out of scope. - del(transform) - -if TEST_buildTransformFromOpenProfiles: - # we'll actually test a couple profile open/creation functions here too - - # first, get a handle to an input profile, in this case we'll create - # an sRGB profile on the fly: - inputProfile = ImageCms.createProfile("sRGB") - - # then, get a handle to the output profile - outputProfile = ImageCms.getOpenProfile(OUTPUT_PROFILE) - - # make a transform from these - transform = ImageCms.buildTransformFromOpenProfiles(inputProfile, \ - outputProfile, INMODE, OUTMODE) - - # now, use the trnsform to convert a couple images - im = Image.open(IMAGE) - - # transform im normally - im2 = ImageCms.applyTransform(im, transform) - outputImage(im2, "buildTransformFromOpenProfiles") - - # then do it again using the same transform, this time in-place. - result = ImageCms.applyTransform(im, transform, inPlace = True) - outputImage(im, "buildTransformFromOpenProfiles_inPlace") - - print("buildTransformFromOpenProfiles test completed successfully.") - - # and, to clean up a bit, delete the transform - # this should call the C destructor for the each item. - # Python should also do this automatically when it goes out of scope. - del(inputProfile) - del(outputProfile) - del(transform) - -if TEST_buildProofTransform: - # make a transform using the input and output and proof profile path - # strings - # images converted with this transform will simulate the appearance - # of the output device while actually being displayed/proofed on the - # proof device. This usually means a monitor, but can also mean - # other proof-printers like dye-sub, etc. - transform = ImageCms.buildProofTransform(INPUT_PROFILE, OUTPUT_PROFILE, \ - PROOF_PROFILE, INMODE, OUTMODE) - - # now, use the trnsform to convert a couple images - im = Image.open(IMAGE) - - # transform im normally - im2 = ImageCms.applyTransform(im, transform) - outputImage(im2, "buildProofTransform") - - # then transform it again using the same transform, this time in-place. - result = ImageCms.applyTransform(im, transform, inPlace = True) - outputImage(im, "buildProofTransform_inPlace") - - print("buildProofTransform test completed successfully.") - - # and, to clean up a bit, delete the transform - # this should call the C destructor for the transform structure. - # Python should also do this automatically when it goes out of scope. - del(transform) - -if TEST_getProfileInfo: - # get a profile handle - profile = ImageCms.getOpenProfile(INPUT_PROFILE) - - # lets print some info about our input profile: - print("Profile name (retrieved from profile string path name): %s" %ImageCms.getProfileName(INPUT_PROFILE)) - - # or, you could do the same thing using a profile handle as the arg - print("Profile name (retrieved from profile handle): %s" %ImageCms.getProfileName(profile)) - - # now lets get the embedded "info" tag contents - # once again, you can use a path to a profile, or a profile handle - print("Profile info (retrieved from profile handle): %s" %ImageCms.getProfileInfo(profile)) - - # and what's the default intent of this profile? - print("The default intent is (this will be an integer): %d" %(ImageCms.getDefaultIntent(profile))) - - # Hmmmm... but does this profile support INTENT_ABSOLUTE_COLORIMETRIC? - print("Does it support INTENT_ABSOLUTE_COLORIMETRIC?: (1 is yes, -1 is no): %s" \ - %ImageCms.isIntentSupported(profile, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, \ - ImageCms.DIRECTION_INPUT)) - - print("getProfileInfo test completed successfully.") - -if TEST_misc: - # test the versions, about, and copyright functions - print("Versions: %s" %str(ImageCms.versions())) - print("About:\n\n%s" %ImageCms.about()) - print("Copyright:\n\n%s" %ImageCms.copyright()) - - print("misc test completed successfully.") - From 2004fd0845fadf060fc2e12f2860830aabe3dfc7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:43:44 -0400 Subject: [PATCH 190/488] Prefer rst --- Tests/{README.md => README.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/{README.md => README.rst} (100%) diff --git a/Tests/README.md b/Tests/README.rst similarity index 100% rename from Tests/README.md rename to Tests/README.rst From 2670204dbd5b9238d50a91fed0d64cff5af37794 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:44:28 -0400 Subject: [PATCH 191/488] Clean up --- Tests/README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/README.rst b/Tests/README.rst index 84e2c4655..5f828a034 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -1,7 +1,11 @@ -Pillow test files. +Pillow Tests +============ Test scripts are named `test_xxx.py` and use the `unittest` module. A base class and helper functions can be found in `helper.py`. +Execution +--------- + Run the tests from the root of the Pillow source distribution: python selftest.py From 2effb0d429b05dfaf142ec2a2b4bb3f1c74320a7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:44:53 -0400 Subject: [PATCH 192/488] Fix error(s) --- Tests/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/README.rst b/Tests/README.rst index 5f828a034..be6da8722 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -6,12 +6,12 @@ Test scripts are named `test_xxx.py` and use the `unittest` module. A base class Execution --------- -Run the tests from the root of the Pillow source distribution: +Run the tests from the root of the Pillow source distribution:: python selftest.py nosetests Tests/test_*.py -Or with coverage: +Or with coverage:: coverage run --append --include=PIL/* selftest.py coverage run --append --include=PIL/* -m nose Tests/test_*.py @@ -19,6 +19,6 @@ Or with coverage: coverage html open htmlcov/index.html -To run an individual test: +To run an individual test:: python Tests/test_image.py From a45c8aaf61029bad7fb1e49edc1e8ed811979c94 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:45:39 -0400 Subject: [PATCH 193/488] Fix error(s) --- Tests/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/README.rst b/Tests/README.rst index be6da8722..1eaa4f3a2 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -1,7 +1,7 @@ Pillow Tests ============ -Test scripts are named `test_xxx.py` and use the `unittest` module. A base class and helper functions can be found in `helper.py`. +Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``. Execution --------- From c261674980956361d3b98b6239687e29d45b8405 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 27 Jun 2014 15:58:12 +0300 Subject: [PATCH 194/488] Remove obsolete Animated Raster Graphics support --- PIL/ArgImagePlugin.py | 506 ------------------------------------------ PIL/__init__.py | 3 +- Scripts/player.py | 23 -- docs/plugins.rst | 8 - 4 files changed, 1 insertion(+), 539 deletions(-) delete mode 100644 PIL/ArgImagePlugin.py diff --git a/PIL/ArgImagePlugin.py b/PIL/ArgImagePlugin.py deleted file mode 100644 index 7fc167c60..000000000 --- a/PIL/ArgImagePlugin.py +++ /dev/null @@ -1,506 +0,0 @@ -# -# THIS IS WORK IN PROGRESS -# -# The Python Imaging Library. -# $Id$ -# -# ARG animation support code -# -# history: -# 1996-12-30 fl Created -# 1996-01-06 fl Added safe scripting environment -# 1996-01-10 fl Added JHDR, UHDR and sYNC support -# 2005-03-02 fl Removed AAPP and ARUN support -# -# Copyright (c) Secret Labs AB 1997. -# Copyright (c) Fredrik Lundh 1996-97. -# -# See the README file for information on usage and redistribution. -# - -from __future__ import print_function - -__version__ = "0.4" - -from PIL import Image, ImageFile, ImagePalette - -from PIL.PngImagePlugin import i8, i16, i32, ChunkStream, _MODES - -MAGIC = b"\212ARG\r\n\032\n" - -# -------------------------------------------------------------------- -# ARG parser - -class ArgStream(ChunkStream): - "Parser callbacks for ARG data" - - def __init__(self, fp): - - ChunkStream.__init__(self, fp) - - self.eof = 0 - - self.im = None - self.palette = None - - self.__reset() - - def __reset(self): - - # reset decoder state (called on init and sync) - - self.count = 0 - self.id = None - self.action = ("NONE",) - - self.images = {} - self.names = {} - - - def chunk_AHDR(self, offset, bytes): - "AHDR -- animation header" - - # assertions - if self.count != 0: - raise SyntaxError("misplaced AHDR chunk") - - s = self.fp.read(bytes) - self.size = i32(s), i32(s[4:]) - try: - self.mode, self.rawmode = _MODES[(i8(s[8]), i8(s[9]))] - except: - raise SyntaxError("unknown ARG mode") - - if Image.DEBUG: - print("AHDR size", self.size) - print("AHDR mode", self.mode, self.rawmode) - - return s - - def chunk_AFRM(self, offset, bytes): - "AFRM -- next frame follows" - - # assertions - if self.count != 0: - raise SyntaxError("misplaced AFRM chunk") - - self.show = 1 - self.id = 0 - self.count = 1 - self.repair = None - - s = self.fp.read(bytes) - if len(s) >= 2: - self.id = i16(s) - if len(s) >= 4: - self.count = i16(s[2:4]) - if len(s) >= 6: - self.repair = i16(s[4:6]) - else: - self.repair = None - - if Image.DEBUG: - print("AFRM", self.id, self.count) - - return s - - def chunk_ADEF(self, offset, bytes): - "ADEF -- store image" - - # assertions - if self.count != 0: - raise SyntaxError("misplaced ADEF chunk") - - self.show = 0 - self.id = 0 - self.count = 1 - self.repair = None - - s = self.fp.read(bytes) - if len(s) >= 2: - self.id = i16(s) - if len(s) >= 4: - self.count = i16(s[2:4]) - - if Image.DEBUG: - print("ADEF", self.id, self.count) - - return s - - def chunk_NAME(self, offset, bytes): - "NAME -- name the current image" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced NAME chunk") - - name = self.fp.read(bytes) - self.names[self.id] = name - - return name - - def chunk_AEND(self, offset, bytes): - "AEND -- end of animation" - - if Image.DEBUG: - print("AEND") - - self.eof = 1 - - raise EOFError("end of ARG file") - - def __getmodesize(self, s, full=1): - - size = i32(s), i32(s[4:]) - - try: - mode, rawmode = _MODES[(i8(s[8]), i8(s[9]))] - except: - raise SyntaxError("unknown image mode") - - if full: - if i8(s[12]): - pass # interlace not yet supported - if i8(s[11]): - raise SyntaxError("unknown filter category") - - return size, mode, rawmode - - def chunk_PAST(self, offset, bytes): - "PAST -- paste one image into another" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced PAST chunk") - - if self.repair is not None: - # we must repair the target image before we - # start pasting - - # brute force; a better solution would be to - # update only the dirty rectangles in images[id]. - # note that if images[id] doesn't exist, it must - # be created - - self.images[self.id] = self.images[self.repair].copy() - self.repair = None - - s = self.fp.read(bytes) - im = self.images[i16(s)] - x, y = i32(s[2:6]), i32(s[6:10]) - bbox = x, y, im.size[0]+x, im.size[1]+y - - if im.mode in ["RGBA"]: - # paste with transparency - # FIXME: should handle P+transparency as well - self.images[self.id].paste(im, bbox, im) - else: - # paste without transparency - self.images[self.id].paste(im, bbox) - - self.action = ("PAST",) - self.__store() - - return s - - def chunk_BLNK(self, offset, bytes): - "BLNK -- create blank image" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced BLNK chunk") - - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s, 0) - - # store image (FIXME: handle colour) - self.action = ("BLNK",) - self.im = Image.core.fill(mode, size, 0) - self.__store() - - return s - - def chunk_IHDR(self, offset, bytes): - "IHDR -- full image follows" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced IHDR chunk") - - # image header - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s) - - # decode and store image - self.action = ("IHDR",) - self.im = Image.core.new(mode, size) - self.decoder = Image.core.zip_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - self.data = b"" - - return s - - def chunk_DHDR(self, offset, bytes): - "DHDR -- delta image follows" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced DHDR chunk") - - s = self.fp.read(bytes) - - size, mode, rawmode = self.__getmodesize(s) - - # delta header - diff = i8(s[13]) - offs = i32(s[14:18]), i32(s[18:22]) - - bbox = offs + (offs[0]+size[0], offs[1]+size[1]) - - if Image.DEBUG: - print("DHDR", diff, bbox) - - # FIXME: decode and apply image - self.action = ("DHDR", diff, bbox) - - # setup decoder - self.im = Image.core.new(mode, size) - - self.decoder = Image.core.zip_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - - self.data = b"" - - return s - - def chunk_JHDR(self, offset, bytes): - "JHDR -- JPEG image follows" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced JHDR chunk") - - # image header - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s, 0) - - # decode and store image - self.action = ("JHDR",) - self.im = Image.core.new(mode, size) - self.decoder = Image.core.jpeg_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - self.data = b"" - - return s - - def chunk_UHDR(self, offset, bytes): - "UHDR -- uncompressed image data follows (EXPERIMENTAL)" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced UHDR chunk") - - # image header - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s, 0) - - # decode and store image - self.action = ("UHDR",) - self.im = Image.core.new(mode, size) - self.decoder = Image.core.raw_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - self.data = b"" - - return s - - def chunk_IDAT(self, offset, bytes): - "IDAT -- image data block" - - # pass compressed chunks through the decoder - s = self.fp.read(bytes) - self.data = self.data + s - n, e = self.decoder.decode(self.data) - if n < 0: - # end of image - if e < 0: - raise IOError("decoder error %d" % e) - else: - self.data = self.data[n:] - - return s - - def chunk_DEND(self, offset, bytes): - return self.chunk_IEND(offset, bytes) - - def chunk_JEND(self, offset, bytes): - return self.chunk_IEND(offset, bytes) - - def chunk_UEND(self, offset, bytes): - return self.chunk_IEND(offset, bytes) - - def chunk_IEND(self, offset, bytes): - "IEND -- end of image" - - # we now have a new image. carry out the operation - # defined by the image header. - - # won't need these anymore - del self.decoder - del self.data - - self.__store() - - return self.fp.read(bytes) - - def __store(self): - - # apply operation - cid = self.action[0] - - if cid in ["BLNK", "IHDR", "JHDR", "UHDR"]: - # store - self.images[self.id] = self.im - - elif cid == "DHDR": - # paste - cid, mode, bbox = self.action - im0 = self.images[self.id] - im1 = self.im - if mode == 0: - im1 = im1.chop_add_modulo(im0.crop(bbox)) - im0.paste(im1, bbox) - - self.count -= 1 - - if self.count == 0 and self.show: - self.im = self.images[self.id] - raise EOFError # end of this frame - - def chunk_PLTE(self, offset, bytes): - "PLTE -- palette data" - - s = self.fp.read(bytes) - if self.mode == "P": - self.palette = ImagePalette.raw("RGB", s) - return s - - def chunk_sYNC(self, offset, bytes): - "SYNC -- reset decoder" - - if self.count != 0: - raise SyntaxError("misplaced sYNC chunk") - - s = self.fp.read(bytes) - self.__reset() - return s - - -# -------------------------------------------------------------------- -# ARG reader - -def _accept(prefix): - return prefix[:8] == MAGIC - -## -# Image plugin for the experimental Animated Raster Graphics format. - -class ArgImageFile(ImageFile.ImageFile): - - format = "ARG" - format_description = "Animated raster graphics" - - def _open(self): - - if Image.warnings: - Image.warnings.warn( - "The ArgImagePlugin driver is obsolete, and will be removed " - "from a future release of PIL. If you rely on this module, " - "please contact the PIL authors.", - RuntimeWarning - ) - - if self.fp.read(8) != MAGIC: - raise SyntaxError("not an ARG file") - - self.arg = ArgStream(self.fp) - - # read and process the first chunk (AHDR) - - cid, offset, bytes = self.arg.read() - - if cid != "AHDR": - raise SyntaxError("expected an AHDR chunk") - - s = self.arg.call(cid, offset, bytes) - - self.arg.crc(cid, s) - - # image characteristics - self.mode = self.arg.mode - self.size = self.arg.size - - def load(self): - - if self.arg.im is None: - self.seek(0) - - # image data - self.im = self.arg.im - self.palette = self.arg.palette - - # set things up for further processing - Image.Image.load(self) - - def seek(self, frame): - - if self.arg.eof: - raise EOFError("end of animation") - - self.fp = self.arg.fp - - while True: - - # - # process chunks - - cid, offset, bytes = self.arg.read() - - if self.arg.eof: - raise EOFError("end of animation") - - try: - s = self.arg.call(cid, offset, bytes) - except EOFError: - break - - except "glurk": # AttributeError - if Image.DEBUG: - print(cid, bytes, "(unknown)") - s = self.fp.read(bytes) - - self.arg.crc(cid, s) - - self.fp.read(4) # ship extra CRC - - def tell(self): - return 0 - - def verify(self): - "Verify ARG file" - - # back up to first chunk - self.fp.seek(8) - - self.arg.verify(self) - self.arg.close() - - self.fp = None - -# -# -------------------------------------------------------------------- - -Image.register_open("ARG", ArgImageFile, _accept) - -Image.register_extension("ARG", ".arg") - -Image.register_mime("ARG", "video/x-arg") diff --git a/PIL/__init__.py b/PIL/__init__.py index c35f6207e..14daee5ca 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -14,8 +14,7 @@ VERSION = '1.1.7' # PIL version PILLOW_VERSION = '2.4.0' # Pillow -_plugins = ['ArgImagePlugin', - 'BmpImagePlugin', +_plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', 'CurImagePlugin', 'DcxImagePlugin', diff --git a/Scripts/player.py b/Scripts/player.py index 84b636668..0c90286c5 100644 --- a/Scripts/player.py +++ b/Scripts/player.py @@ -18,25 +18,6 @@ import sys Image.DEBUG = 0 -# -------------------------------------------------------------------- -# experimental: support ARG animation scripts - -import ArgImagePlugin - -def applet_hook(animation, images): - app = animation(animation_display, images) - app.run() - -ArgImagePlugin.APPLET_HOOK = applet_hook - -class AppletDisplay: - def __init__(self, ui): - self.__ui = ui - def paste(self, im, bbox): - self.__ui.image.paste(im, bbox) - def update(self): - self.__ui.update_idletasks() - # -------------------------------------------------------------------- # an image animation player @@ -56,10 +37,6 @@ class UI(Label): else: self.image = ImageTk.PhotoImage(im) - # APPLET SUPPORT (very crude, and not 100% safe) - global animation_display - animation_display = AppletDisplay(self) - Label.__init__(self, master, image=self.image, bg="black", bd=0) self.update() diff --git a/docs/plugins.rst b/docs/plugins.rst index 001cee949..a069f80df 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -1,14 +1,6 @@ Plugin reference ================ -:mod:`ArgImagePlugin` Module ----------------------------- - -.. automodule:: PIL.ArgImagePlugin - :members: - :undoc-members: - :show-inheritance: - :mod:`BmpImagePlugin` Module ---------------------------- From c23f9002507e1a9c9afef1a760df0d866023e324 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 08:57:49 -0700 Subject: [PATCH 195/488] reverted __main__ guard --- setup.py | 70 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/setup.py b/setup.py index e0a22f75f..10ef0db08 100644 --- a/setup.py +++ b/setup.py @@ -705,39 +705,39 @@ class pil_build_ext(build_ext): finally: os.unlink(tmpfile) -if __name__ == '__main__': - setup( - name=NAME, - version=VERSION, - description='Python Imaging Library (Fork)', - long_description=( - _read('README.rst') + b'\n' + - _read('CHANGES.rst')).decode('utf-8'), - author='Alex Clark (fork author)', - author_email='aclark@aclark.net', - url='http://python-pillow.github.io/', - classifiers=[ - "Development Status :: 6 - Mature", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Scanners", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", ], - cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], - include_package_data=True, - packages=find_packages(), - scripts=glob.glob("Scripts/pil*.py"), - test_suite='PIL.tests', - keywords=["Imaging", ], - license='Standard PIL License', - zip_safe=True, - ) + +setup( + name=NAME, + version=VERSION, + description='Python Imaging Library (Fork)', + long_description=( + _read('README.rst') + b'\n' + + _read('CHANGES.rst')).decode('utf-8'), + author='Alex Clark (fork author)', + author_email='aclark@aclark.net', + url='http://python-pillow.github.io/', + classifiers=[ + "Development Status :: 6 - Mature", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", + "Topic :: Multimedia :: Graphics :: Capture :: Scanners", + "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + "Topic :: Multimedia :: Graphics :: Viewers", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", ], + cmdclass={"build_ext": pil_build_ext}, + ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + include_package_data=True, + packages=find_packages(), + scripts=glob.glob("Scripts/pil*.py"), + test_suite='PIL.tests', + keywords=["Imaging", ], + license='Standard PIL License', + zip_safe=True, +) # End of file From 9305e8499bff96814ee07a1d43b77a35f70befe9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 08:58:27 -0700 Subject: [PATCH 196/488] Added python 3.4 --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 10ef0db08..b89a08f41 100644 --- a/setup.py +++ b/setup.py @@ -729,7 +729,9 @@ setup( "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", ], + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + ], cmdclass={"build_ext": pil_build_ext}, ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], include_package_data=True, From c927ab266e95710d6c15b53f4b844c74950dd712 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 27 Jun 2014 21:30:08 +0300 Subject: [PATCH 197/488] Warn about decompression bombs --- docs/reference/Image.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 7125fcad4..6dcb73638 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -49,6 +49,8 @@ Functions .. autofunction:: open + .. warning:: > To protect against potential DOS attacks caused by "[decompression bombs](https://en.wikipedia.org/wiki/Zip_bomb)" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also [the logging documentation](https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module) to have warnings output to the logging facility instead of stderr. + Image processing ^^^^^^^^^^^^^^^^ From e7aee1444c0c2fb48e419e990315298eae86c403 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 16:26:42 -0400 Subject: [PATCH 198/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3f4299f5e..c2ac9f136 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Remove obsolete Animated Raster Graphics (ARG) support + [hugovk] + - Fix test_imagedraw failures #727 [cgohlke] From 5e3bf95c84507a053e771fec86796fdae400a7f1 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 17:43:24 -0400 Subject: [PATCH 199/488] Fix manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 79a70dddd..08bd0b4e3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -54,6 +54,7 @@ recursive-include Tests *.png recursive-include Tests *.ppm recursive-include Tests *.psd recursive-include Tests *.py +recursive-include Tests *.rst recursive-include Tests *.spider recursive-include Tests *.tar recursive-include Tests *.tif From b6d3983c59bdd5f0286823e7afd7a3009ab2bb8a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 16:02:42 -0700 Subject: [PATCH 200/488] Created README --- depends/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 depends/README.md diff --git a/depends/README.md b/depends/README.md new file mode 100644 index 000000000..a94773bb1 --- /dev/null +++ b/depends/README.md @@ -0,0 +1 @@ +Scripts in this directory download, build and install the non-packaged dependencies for testing with Travis CI. From cd7b45994b1b1a016a29401d7ab3faf9b7c7d054 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 25 Jun 2014 21:34:16 -0400 Subject: [PATCH 201/488] Prevent shell injection in load_djpeg --- PIL/JpegImagePlugin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 1d300fc04..8a06cf5a5 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -34,12 +34,18 @@ __version__ = "0.6" +import sys import array import struct from PIL import Image, ImageFile, _binary from PIL.JpegPresets import presets from PIL._util import isStringType +if sys.version_info >= (3, 3): + from shlex import quote +else: + from pipes import quote + i8 = _binary.i8 o8 = _binary.o8 i16 = _binary.i16be @@ -359,7 +365,7 @@ class JpegImageFile(ImageFile.ImageFile): f, path = tempfile.mkstemp() os.close(f) if os.path.exists(self.filename): - os.system("djpeg '%s' >'%s'" % (self.filename, path)) + os.system("djpeg %s > '%s'" % (quote(self.filename), path)) else: raise ValueError("Invalid Filename") From d283f77884754660520005c086aa00cd46918cf9 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 26 Jun 2014 23:37:49 -0400 Subject: [PATCH 202/488] Tests for _save_netpbm, _save_cjpeg and load_djpeg --- Tests/test_file_gif.py | 15 +++++++++++ Tests/test_file_jpeg.py | 18 +++++++++++-- Tests/test_shell_injection.py | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 Tests/test_shell_injection.py diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 01ac1d95c..719847a96 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, tearDownModule, lena from PIL import Image +from PIL import GifImagePlugin codecs = dir(Image.core) @@ -89,6 +90,20 @@ class TestFileGif(PillowTestCase): reloaded = roundtrip(im)[1].convert('RGB') self.assert_image_equal(im, reloaded) + def test_save_netpbm_bmp_mode(self): + img = Image.open(file).convert("RGB") + + tempfile = self.tempfile("temp.gif") + GifImagePlugin._save_netpbm(img, 0, tempfile) + self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) + + def test_save_netpbm_l_mode(self): + img = Image.open(file).convert("L") + + tempfile = self.tempfile("temp.gif") + GifImagePlugin._save_netpbm(img, 0, tempfile) + self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index dae1d0019..5b2ba45e7 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -5,6 +5,7 @@ from io import BytesIO from PIL import Image from PIL import ImageFile +from PIL import JpegImagePlugin codecs = dir(Image.core) @@ -273,8 +274,21 @@ class TestFileJpeg(PillowTestCase): qtables={0:standard_l_qtable, 1:standard_chrominance_qtable}), 30) - - + + def test_load_djpeg(self): + img = Image.open(test_file) + img.load_djpeg() + self.assert_image_similar(img, Image.open(test_file), 0) + + def test_save_cjpeg(self): + img = Image.open(test_file) + + tempfile = self.tempfile("temp.jpg") + JpegImagePlugin._save_cjpeg(img, 0, tempfile) + # Default save quality is 75%, so a tiny bit of difference is alright + self.assert_image_similar(img, Image.open(tempfile), 1) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py new file mode 100644 index 000000000..d20f9b1a6 --- /dev/null +++ b/Tests/test_shell_injection.py @@ -0,0 +1,51 @@ +from helper import unittest, PillowTestCase, tearDownModule + +import shutil + +from PIL import Image, JpegImagePlugin, GifImagePlugin + +test_jpg = "Tests/images/lena.jpg" +test_gif = "Tests/images/lena.gif" + +test_filenames = ( + "temp_';", + "temp_\";", + "temp_'\"|", + "temp_'\"||", + "temp_'\"&&", +) + +class TestShellInjection(PillowTestCase): + + def assert_save_filename_check(self, src_img, save_func): + for filename in test_filenames: + dest_file = self.tempfile(filename) + save_func(src_img, 0, dest_file) + # If file can't be opened, shell injection probably occurred + Image.open(dest_file).load() + + def test_load_djpeg_filename(self): + for filename in test_filenames: + src_file = self.tempfile(filename) + shutil.copy(test_jpg, src_file) + + im = Image.open(src_file) + im.load_djpeg() + + def test_save_cjpeg_filename(self): + im = Image.open(test_jpg) + self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg) + + def test_save_netpbm_filename_bmp_mode(self): + im = Image.open(test_gif).convert("RGB") + self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + + def test_save_netpbm_filename_l_mode(self): + im = Image.open(test_gif).convert("L") + self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 34317edd8ace0f09d510b1b551034eab0a368c1b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 27 Jun 2014 00:28:44 -0400 Subject: [PATCH 203/488] Change most uses of os.system to use subprocess The only places left that use os.system are in ImageShow and setup.py --- PIL/GifImagePlugin.py | 15 +++++++++++---- PIL/JpegImagePlugin.py | 10 +++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index c6d449425..7f29ffe12 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -333,13 +333,20 @@ def _save_netpbm(im, fp, filename): # below for information on how to enable this. import os + from subprocess import Popen, check_call, PIPE file = im._dump() if im.mode != "RGB": - os.system("ppmtogif %s >%s" % (file, filename)) + with open(filename, 'wb') as f: + check_call(["ppmtogif", file], stdout=f) else: - os.system("ppmquant 256 %s | ppmtogif >%s" % (file, filename)) - try: os.unlink(file) - except: pass + with open(filename, 'wb') as f: + #TODO: What happens if ppmquant fails? + ppmquant_proc = Popen(["ppmquant", "256", file], stdout=PIPE) + check_call(["ppmtogif"], stdin=ppmquant_proc.stdout, stdout=f) + try: + os.unlink(file) + except: + pass # -------------------------------------------------------------------- diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 8a06cf5a5..c159d61c3 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -360,12 +360,14 @@ class JpegImageFile(ImageFile.ImageFile): # ALTERNATIVE: handle JPEGs via the IJG command line utilities + import subprocess import tempfile import os f, path = tempfile.mkstemp() os.close(f) if os.path.exists(self.filename): - os.system("djpeg %s > '%s'" % (quote(self.filename), path)) + with open(path, 'wb') as f: + subprocess.check_call(["djpeg", self.filename], stdout=f) else: raise ValueError("Invalid Filename") @@ -608,8 +610,10 @@ def _save(im, fp, filename): def _save_cjpeg(im, fp, filename): # ALTERNATIVE: handle JPEGs via the IJG command line utilities. import os - file = im._dump() - os.system("cjpeg %s >%s" % (file, filename)) + import subprocess + tempfile = im._dump() + with open(filename, 'wb') as f: + subprocess.check_call(["cjpeg", tempfile], stdout=f) try: os.unlink(file) except: From b5ab5a49e79676e37e3323115e10fa581164a005 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 27 Jun 2014 08:53:34 -0400 Subject: [PATCH 204/488] Add libjpeg-turbo-progs to travis install --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4de1fde99..cde26e9b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ python: - 3.4 install: - - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake" - "pip install cffi" - "pip install coveralls nose" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi From a301d061fbff5c5ec4e6e1ae82a361deb295b7b6 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 27 Jun 2014 11:02:36 -0400 Subject: [PATCH 205/488] Better error checking in _save_netpbm --- PIL/GifImagePlugin.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 7f29ffe12..a195ff8c2 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -333,16 +333,32 @@ def _save_netpbm(im, fp, filename): # below for information on how to enable this. import os - from subprocess import Popen, check_call, PIPE + from subprocess import Popen, check_call, PIPE, CalledProcessError file = im._dump() if im.mode != "RGB": with open(filename, 'wb') as f: check_call(["ppmtogif", file], stdout=f) else: with open(filename, 'wb') as f: - #TODO: What happens if ppmquant fails? - ppmquant_proc = Popen(["ppmquant", "256", file], stdout=PIPE) - check_call(["ppmtogif"], stdin=ppmquant_proc.stdout, stdout=f) + + # Pipe ppmquant output into ppmtogif + # "ppmquant 256 %s | ppmtogif > %s" % (file, filename) + quant_cmd = ["ppmquant", "256", file] + togif_cmd = ["ppmtogif"] + quant_proc = Popen(quant_cmd, stdout=PIPE) + togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f) + + # Allow ppmquant to receive SIGPIPE if ppmtogif exits + quant_proc.stdout.close() + + retcode = quant_proc.wait() + if retcode: + raise CalledProcessError(retcode, quant_cmd) + + retcode = togif_proc.wait() + if retcode: + raise CalledProcessError(retcode, togif_cmd) + try: os.unlink(file) except: From 8b365f542a7232cb324ba25ef93aa8079a3703b3 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 27 Jun 2014 18:12:37 -0400 Subject: [PATCH 206/488] Skip tests if external commands aren't found --- Tests/helper.py | 25 +++++++++++++++++++++++++ Tests/test_file_gif.py | 4 +++- Tests/test_file_jpeg.py | 3 +++ Tests/test_shell_injection.py | 5 +++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Tests/helper.py b/Tests/helper.py index 051912897..104b8cc3e 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -200,4 +200,29 @@ def lena(mode="RGB", cache={}): # cache[mode] = im return im + +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. + """ + import os + import subprocess + with open(os.devnull, 'w') as f: + try: + subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT).wait() + except OSError: + return False + return True + +def djpeg_available(): + return command_succeeds(['djpeg', '--help']) + +def cjpeg_available(): + return command_succeeds(['cjpeg', '--help']) + +def netpbm_available(): + return command_succeeds(["ppmquant", "--help"]) and \ + command_succeeds(["ppmtogif", "--help"]) + # End of file diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 719847a96..e31779df0 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, tearDownModule, lena, netpbm_available from PIL import Image from PIL import GifImagePlugin @@ -90,6 +90,7 @@ class TestFileGif(PillowTestCase): reloaded = roundtrip(im)[1].convert('RGB') self.assert_image_equal(im, reloaded) + @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_bmp_mode(self): img = Image.open(file).convert("RGB") @@ -97,6 +98,7 @@ class TestFileGif(PillowTestCase): GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) + @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_l_mode(self): img = Image.open(file).convert("L") diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 5b2ba45e7..283c48eb7 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,4 +1,5 @@ from helper import unittest, PillowTestCase, tearDownModule, lena, py3 +from helper import djpeg_available, cjpeg_available import random from io import BytesIO @@ -275,11 +276,13 @@ class TestFileJpeg(PillowTestCase): 1:standard_chrominance_qtable}), 30) + @unittest.skipUnless(djpeg_available(), "djpeg not available") def test_load_djpeg(self): img = Image.open(test_file) img.load_djpeg() self.assert_image_similar(img, Image.open(test_file), 0) + @unittest.skipUnless(cjpeg_available(), "cjpeg not available") def test_save_cjpeg(self): img = Image.open(test_file) diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index d20f9b1a6..fd14e5f44 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,4 +1,5 @@ from helper import unittest, PillowTestCase, tearDownModule +from helper import djpeg_available, cjpeg_available, netpbm_available import shutil @@ -24,6 +25,7 @@ class TestShellInjection(PillowTestCase): # If file can't be opened, shell injection probably occurred Image.open(dest_file).load() + @unittest.skipUnless(djpeg_available(), "djpeg not available") def test_load_djpeg_filename(self): for filename in test_filenames: src_file = self.tempfile(filename) @@ -32,14 +34,17 @@ class TestShellInjection(PillowTestCase): im = Image.open(src_file) im.load_djpeg() + @unittest.skipUnless(cjpeg_available(), "cjpeg not available") def test_save_cjpeg_filename(self): im = Image.open(test_jpg) self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg) + @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_filename_bmp_mode(self): im = Image.open(test_gif).convert("RGB") self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + @unittest.skipUnless(netpbm_available(), "netpbm not available") def test_save_netpbm_filename_l_mode(self): im = Image.open(test_gif).convert("L") self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) From 2f09622516c92ba564ad3453d69b44b1277e2481 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 19:13:00 -0400 Subject: [PATCH 207/488] Top level flake8 fixes --- mp_compile.py | 22 ++++++++++++---------- setup.py | 5 +---- test-installed.py | 6 +++--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/mp_compile.py b/mp_compile.py index 0ff5b4b62..71b4089e1 100644 --- a/mp_compile.py +++ b/mp_compile.py @@ -5,6 +5,7 @@ from multiprocessing import Pool, cpu_count from distutils.ccompiler import CCompiler import os + # hideous monkeypatching. but. but. but. def _mp_compile_one(tp): (self, obj, build, cc_args, extra_postargs, pp_opts) = tp @@ -15,21 +16,20 @@ def _mp_compile_one(tp): self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) return + def _mp_compile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None): """Compile one or more source files. - + see distutils.ccompiler.CCompiler.compile for comments. """ # A concrete compiler class can either override this method # entirely or implement _compile(). - - macros, objects, extra_postargs, pp_opts, build = \ - self._setup_compile(output_dir, macros, include_dirs, sources, - depends, extra_postargs) - cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) + macros, objects, extra_postargs, pp_opts, build = self._setup_compile( + output_dir, macros, include_dirs, sources, depends, extra_postargs) + cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) try: max_procs = int(os.environ.get('MAX_CONCURRENCY', cpu_count())) @@ -38,10 +38,12 @@ def _mp_compile(self, sources, output_dir=None, macros=None, pool = Pool(max_procs) try: print ("Building using %d processes" % pool._processes) - except: pass - arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects] - results = pool.map_async(_mp_compile_one,arr) - + except: + pass + arr = [ + (self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects + ] + pool.map_async(_mp_compile_one, arr) pool.close() pool.join() # Return *all* object filenames, not just the ones we just built. diff --git a/setup.py b/setup.py index 1a31a8981..e0a22f75f 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,6 @@ import re import struct import sys -import mp_compile - from distutils.command.build_ext import build_ext from distutils import sysconfig from setuptools import Extension, setup, find_packages @@ -707,7 +705,7 @@ class pil_build_ext(build_ext): finally: os.unlink(tmpfile) -if __name__=='__main__': +if __name__ == '__main__': setup( name=NAME, version=VERSION, @@ -742,5 +740,4 @@ if __name__=='__main__': license='Standard PIL License', zip_safe=True, ) - # End of file diff --git a/test-installed.py b/test-installed.py index 7f58f4966..0248590a5 100755 --- a/test-installed.py +++ b/test-installed.py @@ -4,8 +4,8 @@ import os import sys import glob -# monkey with the path, removing the local directory but adding the Tests/ directory -# for helper.py and the other local imports there. +# monkey with the path, removing the local directory but adding the Tests/ +# directory for helper.py and the other local imports there. del(sys.path[0]) sys.path.insert(0, os.path.abspath('./Tests')) @@ -16,7 +16,7 @@ sys.path.insert(0, os.path.abspath('./Tests')) if len(sys.argv) == 1: sys.argv.extend(glob.glob('Tests/test*.py')) -# Make sure that nose doesn't muck with our paths. +# Make sure that nose doesn't muck with our paths. if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): sys.argv.insert(1, '--no-path-adjustment') From 254d64cf8ba4a517425ca428fca542a9c05264cb Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 28 Jun 2014 11:02:09 +0300 Subject: [PATCH 208/488] Show coverage and quality reports for just the committed diff --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4de1fde99..68c65eb8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,13 @@ script: after_success: - coverage report - coveralls + + - pip install diff_cover + - coverage xml + - diff-cover coverage.xml + - diff-quality --violation=pep8 + - diff-quality --violation=pyflakes + - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py - pep8 --statistics --count Tests/*.py From ae82c079df02f0e4848ca602d72ac209d0f62923 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 28 Jun 2014 11:18:33 +0300 Subject: [PATCH 209/488] Fetch the remote master branch before running diff-cover --- .travis.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68c65eb8f..adaf54b6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,14 +42,15 @@ after_success: - coverage report - coveralls - - pip install diff_cover - - coverage xml - - diff-cover coverage.xml - - diff-quality --violation=pep8 - - diff-quality --violation=pyflakes - - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py - pep8 --statistics --count Tests/*.py - pyflakes PIL/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l) + + - time git fetch origin master:refs/remotes/origin/master + - time pip install diff_cover + - time coverage xml + - time diff-cover coverage.xml + - time diff-quality --violation=pep8 + - time diff-quality --violation=pyflakes From 5fd20b9f242f7305b4c9376d85efa8225687afc2 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:07:38 -0400 Subject: [PATCH 210/488] Prefer .rst --- depends/{README.md => README.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename depends/{README.md => README.rst} (100%) diff --git a/depends/README.md b/depends/README.rst similarity index 100% rename from depends/README.md rename to depends/README.rst From 8c8a9ef83f79736deefe3ba90f800b4ec13d4c12 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:10:42 -0400 Subject: [PATCH 211/488] Update --- depends/README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/depends/README.rst b/depends/README.rst index a94773bb1..62c101ecf 100644 --- a/depends/README.rst +++ b/depends/README.rst @@ -1 +1,4 @@ -Scripts in this directory download, build and install the non-packaged dependencies for testing with Travis CI. +Depends +======= + +Scripts in this directory can be used to download, build & install non-packaged dependencies; useful for testing with Travis CI. From 31e2e95533ec521b991872dc0664dd0cd7d050b4 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:14:35 -0400 Subject: [PATCH 212/488] Fix manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 08bd0b4e3..7c783f1b8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -67,6 +67,7 @@ recursive-include Tk *.c recursive-include Tk *.txt recursive-include Tk *.rst recursive-include depends *.sh +recursive-include depends *.rst recursive-include docs *.bat recursive-include docs *.gitignore recursive-include docs *.html From 2d7f0c06d0254ced28849494a10e93cdf5f36b5b Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:15:53 -0400 Subject: [PATCH 213/488] Add testing deps --- MANIFEST.in | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index 7c783f1b8..00c132d47 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include *.c include *.h include *.py include *.rst +include *.txt include .coveragerc include .gitattributes include .travis.yml diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..f3c7e8e6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +nose From 103354facc79f48f6bdc140fb9a1f85c10ae9e18 Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 22:18:47 +0200 Subject: [PATCH 214/488] BMP now uses a reasonable resolution, and customizable using the "dpi" option. --- PIL/BmpImagePlugin.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index 436ca5dce..c20ba184a 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -203,30 +203,37 @@ def _save(im, fp, filename, check=0): if check: return check + info = im.encoderinfo + + dpi = info.get("dpi", (96, 96)) + + # 1 meter == 39.3701 inches + ppm = map(lambda x: int(x * 39.3701), dpi) + stride = ((im.size[0]*bits+7)//8+3)&(~3) header = 40 # or 64 for OS/2 version 2 offset = 14 + header + colors * 4 image = stride * im.size[1] # bitmap header - fp.write(b"BM" + # file type (magic) - o32(offset+image) + # file size - o32(0) + # reserved - o32(offset)) # image data offset + fp.write(b"BM" + # file type (magic) + o32(offset+image) + # file size + o32(0) + # reserved + o32(offset)) # image data offset # bitmap info header - fp.write(o32(header) + # info header size - o32(im.size[0]) + # width - o32(im.size[1]) + # height - o16(1) + # planes - o16(bits) + # depth - o32(0) + # compression (0=uncompressed) - o32(image) + # size of bitmap - o32(1) + o32(1) + # resolution - o32(colors) + # colors used - o32(colors)) # colors important + fp.write(o32(header) + # info header size + o32(im.size[0]) + # width + o32(im.size[1]) + # height + o16(1) + # planes + o16(bits) + # depth + o32(0) + # compression (0=uncompressed) + o32(image) + # size of bitmap + o32(ppm[0]) + o32(ppm[1]) + # resolution + o32(colors) + # colors used + o32(colors)) # colors important - fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) + fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) if im.mode == "1": for i in (0, 255): From ed3cffab6fca734d80f2e5806e579cd4eacd0a9a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:20:12 -0400 Subject: [PATCH 215/488] Add self; make note about reqs I am adding requirements.txt to make it even more obvious we are using nose for testing, and for my own convenience. --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index f3c7e8e6f..86ddbd771 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ +# Testing reqs +-e . nose From b1afb882685ccf2cf015081491621ff9c82a938b Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Jun 2014 23:22:21 +0300 Subject: [PATCH 216/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c2ac9f136..dc95e34bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Pyroma fix and add Python 3.4 to setup metadata #742 + [wirefool] + +- Top level flake8 fixes #741 + [aclark] + - Remove obsolete Animated Raster Graphics (ARG) support [hugovk] From df7d89616e9c2a4f327ced686d36fa9fa9d6025a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:22:52 -0400 Subject: [PATCH 217/488] Run same tests Travis runs --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a226ea602..53c502664 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ pre: bin/python setup.py develop bin/python selftest.py + bin/nosetests Tests/test_*.py check-manifest pyroma . viewdoc From d5bb962f83673f1b498a7ded1a30379d277c2c9a Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 28 Jun 2014 23:48:14 +0300 Subject: [PATCH 218/488] Add test to ensure a Pyroma is a 10/10 Mascarpone --- .travis.yml | 2 +- Tests/test_pyroma.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Tests/test_pyroma.py diff --git a/.travis.yml b/.travis.yml index 4de1fde99..2e6ce3610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls nose" + - "pip install coveralls nose pyroma" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py new file mode 100644 index 000000000..2c2aeae96 --- /dev/null +++ b/Tests/test_pyroma.py @@ -0,0 +1,33 @@ +import unittest + +try: + import pyroma +except ImportError: + # Skip via setUp() + pass + + +class TestPyroma(unittest.TestCase): + + def setUp(self): + try: + import pyroma + except ImportError: + self.skipTest("ImportError") + + def test_pyroma(self): + # Arrange + data = pyroma.projectdata.get_data(".") + + # Act + rating = pyroma.ratings.rate(data) + + # Assert + # Should have a perfect score + self.assertEqual(rating, (10, [])) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 29a65c1373b0f85d9caa627e182afc43e39f8393 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 28 Jun 2014 22:03:40 +0100 Subject: [PATCH 219/488] FIX: fix error for setup.py for Python 3 The subprocess command in Python 3 returns a bytes object. If the homebrew subprocess check returns a not-empty result, then setup crashes trying to combine the bytes with the string constants with and error like "TypeError: Can't mix strings and bytes in path components." --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e0a22f75f..1e634bc12 100644 --- a/setup.py +++ b/setup.py @@ -210,7 +210,9 @@ class pil_build_ext(build_ext): # if Homebrew is installed, use its lib and include directories import subprocess try: - prefix = subprocess.check_output(['brew', '--prefix']).strip() + prefix = subprocess.check_output( + ['brew', '--prefix'] + ).strip().decode('latin1') except: # Homebrew not installed prefix = None From 9318755a180a8e4012ca743865588faad416d2e7 Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 23:21:22 +0200 Subject: [PATCH 220/488] Adds dpi to the Image info dictinoary. --- PIL/BmpImagePlugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index c20ba184a..e320feb7a 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -28,6 +28,7 @@ __version__ = "0.7" from PIL import Image, ImageFile, ImagePalette, _binary +import math i8 = _binary.i8 i16 = _binary.i16le @@ -88,6 +89,7 @@ class BmpImageFile(ImageFile.ImageFile): bits = i16(s[14:]) self.size = i32(s[4:]), i32(s[8:]) compression = i32(s[16:]) + pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter lutsize = 4 colors = i32(s[32:]) direction = -1 @@ -162,6 +164,7 @@ class BmpImageFile(ImageFile.ImageFile): (rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))] self.info["compression"] = compression + self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) def _open(self): @@ -208,7 +211,7 @@ def _save(im, fp, filename, check=0): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = map(lambda x: int(x * 39.3701), dpi) + ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) stride = ((im.size[0]*bits+7)//8+3)&(~3) header = 40 # or 64 for OS/2 version 2 From 92b070ceaf453ef66e8cab91ddd5d59d13e1aa41 Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 23:22:52 +0200 Subject: [PATCH 221/488] Addes tests --- Tests/test_file_bmp.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 29ddb9824..2870aba04 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -37,6 +37,18 @@ class TestFileBmp(PillowTestCase): self.assertEqual(im.size, reloaded.size) self.assertEqual(reloaded.format, "BMP") + def test_dpi(self): + dpi = (72, 72) + + output = io.BytesIO() + im = lena() + im.save(output, "BMP", dpi=dpi) + + output.seek(0) + reloaded = Image.open(output) + + self.assertEqual(reloaded.info["dpi"], dpi) + if __name__ == '__main__': unittest.main() From 2633408dc35eecd53ceec627166ff4aaf1e9d8f1 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 17:48:32 -0400 Subject: [PATCH 222/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dc95e34bf..773dea6e9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Fix error in setup.py for Python 3 + [matthew-brett] + - Pyroma fix and add Python 3.4 to setup metadata #742 [wirefool] From 61be1d8b19452083bad9c897b30be6411d95bf4a Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 23:56:22 +0200 Subject: [PATCH 223/488] dpi key should only be present when there is resolution info in the BMP header. --- PIL/BmpImagePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index e320feb7a..fae6bd391 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -97,6 +97,8 @@ class BmpImageFile(ImageFile.ImageFile): # upside-down storage self.size = self.size[0], 2**32 - self.size[1] direction = 0 + + self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) else: raise IOError("Unsupported BMP header type (%d)" % len(s)) @@ -164,7 +166,6 @@ class BmpImageFile(ImageFile.ImageFile): (rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))] self.info["compression"] = compression - self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) def _open(self): From 526ac7e278ca332684582d28ec46cd14ff70d0bb Mon Sep 17 00:00:00 2001 From: cgohlke Date: Sat, 28 Jun 2014 16:15:06 -0700 Subject: [PATCH 224/488] Fix build failure when compiler.include_dirs refers to nonexistent directory --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f4ce7525e..97ff17d97 100644 --- a/setup.py +++ b/setup.py @@ -405,7 +405,12 @@ class pil_build_ext(build_ext): # Find the best version for directory in self.compiler.include_dirs: - for name in os.listdir(directory): + try: + listdir = os.listdir(directory) + except Exception: + # WindowsError, FileNotFoundError + continue + for name in listdir: if name.startswith('openjpeg-') and \ os.path.isfile(os.path.join(directory, name, 'openjpeg.h')): From 38c3dd8c7dda99e59bea035dbe7c70ef1bee4681 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 29 Jun 2014 10:41:41 +0300 Subject: [PATCH 225/488] Build diff_cover (and more importantly its dependencies such as lxml) with no optimisations to speed up install. Use wheel if possible --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index adaf54b6f..719fc7951 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ after_success: - pyflakes Tests/*.py | tee >(wc -l) - time git fetch origin master:refs/remotes/origin/master - - time pip install diff_cover + - time CFLAGS=-O0 pip install --use-wheel diff_cover - time coverage xml - time diff-cover coverage.xml - time diff-quality --violation=pep8 From 3ea4780bdc600fc26efe7c52e399710988c03034 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 29 Jun 2014 12:39:34 +0300 Subject: [PATCH 226/488] Comment diff-cover --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 719fc7951..d6589efc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,7 @@ after_success: - pyflakes PIL/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l) + # Coverage and quality reports on just the last diff - time git fetch origin master:refs/remotes/origin/master - time CFLAGS=-O0 pip install --use-wheel diff_cover - time coverage xml From 441756919539f1d51040f11e59a04e0d39361eb3 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 29 Jun 2014 08:01:25 -0400 Subject: [PATCH 227/488] Run test-installed.py --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 53c502664..af0d041ad 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ pre: bin/python setup.py develop bin/python selftest.py bin/nosetests Tests/test_*.py + bin/python setup.py install + bin/python test-installed.py check-manifest pyroma . viewdoc From 8534e675469e0c1772cd5b30a0ee373c5b182447 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 29 Jun 2014 08:04:58 -0400 Subject: [PATCH 228/488] Completely automate my pre-release testing routine This Makefile completely automates my pre-release testing routine which typically occurs only in Python 2.7, but gives me a "good enough" view of the status quo. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index af0d041ad..4f6a00711 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ pre: + virtualenv . + bin/pip install -r requirements.txt bin/python setup.py develop bin/python selftest.py bin/nosetests Tests/test_*.py From dba6fd7cd4e1e30bcda4e76e5896c9efdc0f790d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 29 Jun 2014 13:35:11 -0700 Subject: [PATCH 229/488] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 773dea6e9..69279fe47 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Support for Resolution in BMP files #734 + [gcq] + - Fix error in setup.py for Python 3 [matthew-brett] From 5fdc14facb95eec23ee7ba67eafc8d575081452e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 29 Jun 2014 13:46:17 -0700 Subject: [PATCH 230/488] Removing previous approach --- PIL/JpegImagePlugin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index c159d61c3..1e5d57e42 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -34,18 +34,12 @@ __version__ = "0.6" -import sys import array import struct from PIL import Image, ImageFile, _binary from PIL.JpegPresets import presets from PIL._util import isStringType -if sys.version_info >= (3, 3): - from shlex import quote -else: - from pipes import quote - i8 = _binary.i8 o8 = _binary.o8 i16 = _binary.i16be From b981ef425b27bf81930b9c5b775ca311c980aea8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 29 Jun 2014 14:24:32 -0700 Subject: [PATCH 231/488] Suppress stderr from ppmquant and ppmtogif --- PIL/GifImagePlugin.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index a195ff8c2..ec8301973 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -334,10 +334,13 @@ def _save_netpbm(im, fp, filename): import os from subprocess import Popen, check_call, PIPE, CalledProcessError + import tempfile file = im._dump() + if im.mode != "RGB": with open(filename, 'wb') as f: - check_call(["ppmtogif", file], stdout=f) + stderr = tempfile.TemporaryFile() + check_call(["ppmtogif", file], stdout=f, stderr=stderr) else: with open(filename, 'wb') as f: @@ -345,9 +348,11 @@ def _save_netpbm(im, fp, filename): # "ppmquant 256 %s | ppmtogif > %s" % (file, filename) quant_cmd = ["ppmquant", "256", file] togif_cmd = ["ppmtogif"] - quant_proc = Popen(quant_cmd, stdout=PIPE) - togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f) - + stderr = tempfile.TemporaryFile() + quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=stderr) + stderr = tempfile.TemporaryFile() + togif_proc = Popen(togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=stderr) + # Allow ppmquant to receive SIGPIPE if ppmtogif exits quant_proc.stdout.close() From 2d13166667d6bceafa424446dbe4732c095b4f3a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 29 Jun 2014 14:39:32 -0700 Subject: [PATCH 232/488] Don't print the entire image as bytes on failure --- Tests/helper.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 051912897..022eb2d9a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -92,9 +92,8 @@ class PillowTestCase(unittest.TestCase): self.assertEqual( a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size)) - self.assertEqual( - a.tobytes(), b.tobytes(), - msg or "got different content") + if a.tobytes() != b.tobytes(): + self.fail(msg or "got different content") def assert_image_similar(self, a, b, epsilon, msg=None): epsilon = float(epsilon) From bd9c555905af513416af44205e33059988351292 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 30 Jun 2014 02:17:45 +0300 Subject: [PATCH 233/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 69279fe47..ea5f56b95 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Prevent shell injection #748 + [mbrown1413, wiredfool] + - Support for Resolution in BMP files #734 [gcq] From b72b6395c03af36c866b182abc166cfaa4be6acf Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 30 Jun 2014 23:57:16 +0300 Subject: [PATCH 234/488] Don't run on Py3.x because installation takes ~4m , or ~2m with CFLAGS=-O0. Installation is ~1m30s for 2.x or ~30s with flags. --- .travis.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d6589efc7..ec6fb2ce4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,10 +48,14 @@ after_success: - pyflakes PIL/*.py | tee >(wc -l) - pyflakes Tests/*.py | tee >(wc -l) - # Coverage and quality reports on just the last diff - - time git fetch origin master:refs/remotes/origin/master - - time CFLAGS=-O0 pip install --use-wheel diff_cover - - time coverage xml - - time diff-cover coverage.xml - - time diff-quality --violation=pep8 - - time diff-quality --violation=pyflakes + + # Coverage and quality reports on just the latest diff. + # (Installation is very slow on Py3, so just do it for Py2.) + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then + git fetch origin master:refs/remotes/origin/master + time CFLAGS=-O0 pip install --use-wheel diff_cover + coverage xml + diff-cover coverage.xml + diff-quality --violation=pep8 + diff-quality --violation=pyflakes + fi From 50bbb5e0c53590443ff640431acc221a4f4acd1e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 30 Jun 2014 14:12:30 -0700 Subject: [PATCH 235/488] Removed unused function --- libImaging/Unpack.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index 70b11b1b0..552c759b9 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -863,13 +863,6 @@ copy2(UINT8* out, const UINT8* in, int pixels) memcpy(out, in, pixels*2); } -static void -copy3(UINT8* out, const UINT8* in, int pixels) -{ - /* LAB triples, 24bit */ - memcpy(out, in, 3 * pixels); -} - static void copy4(UINT8* out, const UINT8* in, int pixels) { From d002e5f6901c2005083791b453429641f0cb78d0 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 00:22:44 +0300 Subject: [PATCH 236/488] Move diff-cover commands to diffcover.sh --- .travis.yml | 10 ++-------- Scripts/diffcover.sh | 5 +++++ 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100755 Scripts/diffcover.sh diff --git a/.travis.yml b/.travis.yml index ec6fb2ce4..4f15d19d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,11 +51,5 @@ after_success: # Coverage and quality reports on just the latest diff. # (Installation is very slow on Py3, so just do it for Py2.) - - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then - git fetch origin master:refs/remotes/origin/master - time CFLAGS=-O0 pip install --use-wheel diff_cover - coverage xml - diff-cover coverage.xml - diff-quality --violation=pep8 - diff-quality --violation=pyflakes - fi + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then git fetch origin master:refs/remotes/origin/master; fi + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover.sh; fi diff --git a/Scripts/diffcover.sh b/Scripts/diffcover.sh new file mode 100755 index 000000000..a573c97d4 --- /dev/null +++ b/Scripts/diffcover.sh @@ -0,0 +1,5 @@ +time CFLAGS=-O0 pip install --use-wheel diff_cover +coverage xml +diff-cover coverage.xml +diff-quality --violation=pep8 +diff-quality --violation=pyflakes From 3e8593e1b2e5c17c2e3f75da4d32df12fed5ee98 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 30 Jun 2014 14:33:41 -0700 Subject: [PATCH 237/488] Reenable mp_compile --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 97ff17d97..9341b93bb 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,9 @@ from distutils.command.build_ext import build_ext from distutils import sysconfig from setuptools import Extension, setup, find_packages +# monkey patch import hook. Even though flake8 says it's not used, it is. +# comment this out to disable multi threaded builds. +import mp_compile _IMAGING = ( "decode", "encode", "map", "display", "outline", "path") From 668141a898c6b89eec921522650891a8ac899751 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 30 Jun 2014 15:03:57 -0700 Subject: [PATCH 238/488] updated imagedraw tests --- Tests/images/imagedraw_ellipse.png | Bin 491 -> 466 bytes Tests/images/imagedraw_line.png | Bin 286 -> 247 bytes Tests/images/imagedraw_pieslice.png | Bin 405 -> 394 bytes Tests/images/imagedraw_polygon.png | Bin 292 -> 293 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Tests/images/imagedraw_ellipse.png b/Tests/images/imagedraw_ellipse.png index fb03fd148597a95bd4c446243b064e91561973ff..b52b128023c1c7126d5bdf76e82a8b682a0a176a 100644 GIT binary patch delta 440 zcmaFOe2IC2O8rbv7srr_Id5-Y>^o$@;}SSs|NZ=Oc`j9jjcX1X`)&PhqnyO1&a2eP zfB=7PUWz@CSpB`}U(juB38ScZ@y&gK+pgX*{qXkif1wN7-yhsL`up%NrDcKcDbF@+ z>sX&G6{~Dq+tr;uqhp2ttABa<@_eQ?_d6AqsZXmHGcK8Cx6)wi3@aV)AnV$=U*|rl zq)K{AT}$)L+OD_j*`?OhDkn?#@Um(9*UBARd)`|l>^O@|rdxQ+Rcl7~qf;}iR&QUM zVro(UZcbcf#NE|bS5!$mrmG5sIps!twqbWq4Gk^7X2X4L-4?lJiA;$=2^6A!O1_u+ z?IY8#1xsH`x&3|hrd2cAI9sL$$XzqKxM^$NR&D0FQx$$4PHK84bY%fY_Pq3@Le))~ zH*Uwwy6BpyD_C23l5g8fu^;zNR?OP}o?rHM$E&x8P87^IJ7ao7D@zdAjGHK z1a}4msC#oQ_CVtAciexMZVNL|IyU8EMeLc&*W1=C*}m)0uU$XP8TWqTI2XIeevPo< zSt}*u_N}gS`ZCM<&yhLaIlbq1~ zn-`?2cEm;KT-d>4s&Odl`Miq8?`?t?@9r>37jNCVJ}vW<_qGkb<_Er)9qV4ZsB>Os zOU~Xvi)DQ0_8fU0)SfD{=Sy}0@8lrwxM^2Uo%2eJ*er2iw$++$Z!g_(z%L9D{6ARszl^;hS($PRBwbt7#J4x z7$)^Bm;Y!U=WoA2?6FnEHI|K=b{?6V>MM}VI9+0zsp}fkm_;Do%xBXxB887ct^BMS z1ym&V`>BxkyjPP_-!6Ui^;CbU$*pZ)^6lSvt=W6%`a==D5Ye5dSH)@xB~R5Cot|79 zlhhtGanl;LrH|^?*e!kJx2AY5ABYN@?G^A|UX}q3ta!}3Y_APBhp4XwNXFCE&t;uc GLK6T!bX%kV delta 258 zcmey)IFD(9O8rw$7srr_Id5+o@*Oe|a6Q=i`~FRlMh&l)k7}6_=R3IWe0bu|rNqF{ z@c-kBnrpY72fV-HGEcevTGr&(M~fDgwdIL*UyPV0VV@lNXqu#&N>b#b_Yu=}7#(hj zS#xr_g~(|k@w6Ee`b<9yxhF>^%}6{oGvczIq2Otuvjz`S*8CHyj;`L{b@HfGU}iUc+SF*n4YaBvzCN>`9Ja9i$4>>bw#(^ z^Kwt`s!qPDC%QaYcJ~y0(eS)$+s^SUWk3h7su(~0(X!sjaqS>T%G1@)Wt~$(69DQ9 Bb`<~s diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png index 1b2acff92ab9aa263ce85ceb1a37493d4cbef6cb..2f8c091915ff0b92b268661484a53660e1abf136 100644 GIT binary patch delta 367 zcmV-#0g(Qc1BwHXB!5v!L_t(|obB4dj>0euMbVe}|9{RbszR%ws8Q|2_S~~%LBo{| zNUa0_00000000000JyEKKERd!=auzGU4McLy&rP5(=nHwIY+FcL%7+8|CxNf=X%%P zv47fDNBLrH&dDap8f!DTSMFGPkJyZrOdo4AXT*xc(v)6xVt?t~%S)+P((C)nMYUKv zmtF;9`7L9?SaMs&nz44qy~@V&SjMukJXDTl{>@c6mN{Y#_z$tnHnDgt+uy|Ev1}1* z0I>!TYe3jot;M^g88(&z_KF-!(Ru}srBJ=%$I_EtwxLtR(zE$83zj98BJ5+LSX$mI zS1b+nYE8D!aA>8ArG@jAF_!N1@(7wVmhR1$aj?9x^re^6o~DkakG=L+?1N>GrHlD; z8La}bbnIpRcj0syr}tgqb#tQ9hjKLav3LLg00000000000002^<`-z9H)g~J$2kB1 N002ovPDHLkV1nlcxC;OP delta 378 zcmeBTp2|Eyr9Q^f#WAE}&f8lT`D@AH#EY)kO>DGhb?^=Pvrfa=}fN z0S*r2rha2Mccp&*8TnU#1m~>SdOFnWf2-F_<2Mo;t72DNZ2m7p$<`^Az4^OU?3QJZ-bS|Ha(ni>IO+VFyE9a0)~ifw`*KRyd+qf5|E6k9`dMdka4gKN7L)a<_ZpUhB`NEh6qj?0WHZi=XrH zONsjX#cwYgNh{x{*6sdKD7F0YxefMnPJCG`!&|E>2M;9>W8EJnR~h?^ S@T=4GL0nH)KbLh*2~7a8ezuVS diff --git a/Tests/images/imagedraw_polygon.png b/Tests/images/imagedraw_polygon.png index 5f160be76ceef3d85c7092a63d87e48115ec3303..7199a25dd7789faf617b9a518e1f306676c69ac5 100644 GIT binary patch delta 266 zcmZ3&w3KOrO8py87srr_Id5-T=N&N+a5=c_eSNys0@_7DH@%`hro;7xT+4xnKG&+;`iN@*3$JgB(7cds zdaZxD#hkm@&o{L-SHJC_T3j95@%6YWpZm9}lt|C0s!MWJKUUiay*?4NQ|k40Z=05;BxTNT4527UjzX}^Oe0~%;x-oyN!%|$54V(Na7l&7no K%Q|MKgeCx~&U>o> delta 265 zcmZ3=w1jDbO8sk37srr_Id5+q%sOl!;&L(Y|Nr?{TX%8gPRyCPrRw>b#|e6$IaC-K z82B#z-hKap{L3oci`fS?mR_H7Pk8UMms3^bz30EnTwAbyYlP`T{G!}cA2n#ybX zz0@$w>)WjUt5I>MLtdBGMmn#ID^Fe;w0>#C{g_7qORl%2@`_gF)^|^3+8dVqx1#g# z&AnxM*%mvl|BJBP9=`ia$a`V_ZC_U3|L< Date: Wed, 25 Jun 2014 15:01:47 -0700 Subject: [PATCH 239/488] Make nose run tests in parallel --- .travis.yml | 2 +- test-installed.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d4880847c..800fac43a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python notifications: irc: "chat.freenode.net#pil" -env: MAX_CONCURRENCY=4 +env: MAX_CONCURRENCY=4 NOSE_PROCESSES=4 NOSE_PROCESS_TIMEOUT=30 python: - "pypy" diff --git a/test-installed.py b/test-installed.py index 0248590a5..0fed377b8 100755 --- a/test-installed.py +++ b/test-installed.py @@ -20,5 +20,13 @@ if len(sys.argv) == 1: if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): sys.argv.insert(1, '--no-path-adjustment') +if 'NOSE_PROCESSES' not in os.environ: + for arg in sys.argv: + if '--processes' in arg: + break + else: # for + sys.argv.insert(1, '--processes=-1') # -1 == number of cores + sys.argv.insert(1, '--process-timeout=30') + if __name__ == '__main__': nose.main() From c824a15fe8d45535ce5ace70ea0d53bcd4305e40 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 25 Jun 2014 17:08:24 -0700 Subject: [PATCH 240/488] Thread and race condition safe tempfiles for testing --- Tests/helper.py | 72 +++++++++++++----------------------------- Tests/test_font_pcf.py | 3 +- 2 files changed, 24 insertions(+), 51 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 416586c78..9a18f0202 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -3,39 +3,30 @@ Helper functions. """ from __future__ import print_function import sys +import tempfile +import os +import glob if sys.version_info[:2] <= (2, 6): import unittest2 as unittest else: import unittest - -# This should be imported into every test_XXX.py file to report -# any remaining temp files at the end of the run. def tearDownModule(): - import glob - import os - import tempfile - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - tempfiles = glob.glob(os.path.join(temp_root, "temp_*")) - if tempfiles: - print("===", "remaining temporary files") - for file in tempfiles: - print(file) - print("-"*68) - + #remove me later + pass class PillowTestCase(unittest.TestCase): - currentResult = None # holds last result object passed to run method - _tempfiles = [] + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + self.currentResult = None # holds last result object passed to run method def run(self, result=None): - self.addCleanup(self.delete_tempfiles) self.currentResult = result # remember result for use later unittest.TestCase.run(self, result) # call superclass run method - def delete_tempfiles(self): + def delete_tempfile(self, path): try: ok = self.currentResult.wasSuccessful() except AttributeError: # for nosetests @@ -44,19 +35,14 @@ class PillowTestCase(unittest.TestCase): if ok: # only clean out tempfiles if test passed - import os - import os.path - import tempfile - for file in self._tempfiles: - try: - os.remove(file) - except OSError: - pass # report? - temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') try: - os.rmdir(temp_root) + print("Removing File: %s" % path) + os.remove(path) except OSError: - pass + pass # report? + else: + print("=== orphaned temp file") + print(path) def assert_almost_equal(self, a, b, msg=None, eps=1e-6): self.assertLess( @@ -139,27 +125,13 @@ class PillowTestCase(unittest.TestCase): self.assertTrue(found) return result - def tempfile(self, template, *extra): - import os - import os.path - import sys - import tempfile - files = [] - root = os.path.join(tempfile.gettempdir(), 'pillow-tests') - try: - os.mkdir(root) - except OSError: - pass - for temp in (template,) + extra: - assert temp[:5] in ("temp.", "temp_") - name = os.path.basename(sys.argv[0]) - name = temp[:4] + os.path.splitext(name)[0][4:] - name = name + "_%d" % len(self._tempfiles) + temp[4:] - name = os.path.join(root, name) - files.append(name) - self._tempfiles.extend(files) - return files[0] - + def tempfile(self, template): + assert template[:5] in ("temp.", "temp_") + (fd, path) = tempfile.mkstemp(template[4:], template[:4]) + os.close(fd) + + self.addCleanup(self.delete_tempfile, path) + return path # helpers diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 24abcf73d..8c4c04cd4 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -22,7 +22,8 @@ class TestFontPcf(PillowTestCase): self.assertIsInstance(font, FontFile.FontFile) self.assertEqual(len([_f for _f in font.glyph if _f]), 192) - tempname = self.tempfile("temp.pil", "temp.pbm") + tempname = self.tempfile("temp.pil") + self.addCleanup(self.delete_tempfile, tempname[:-4]+'.pbm') font.save(tempname) return tempname From acab4e8fdcfdae19862ddc1f8e30fdde894964a3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 25 Jun 2014 22:47:21 -0700 Subject: [PATCH 241/488] Cleaned up prints --- Tests/helper.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 9a18f0202..1c9851e25 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -36,13 +36,11 @@ class PillowTestCase(unittest.TestCase): if ok: # only clean out tempfiles if test passed try: - print("Removing File: %s" % path) os.remove(path) except OSError: pass # report? else: - print("=== orphaned temp file") - print(path) + print("=== orphaned temp file: %s" %path) def assert_almost_equal(self, a, b, msg=None, eps=1e-6): self.assertLess( From 0ac0e973f0dda9fecf74af05363fa3ebd4311413 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 12:07:34 -0700 Subject: [PATCH 242/488] Ignore debugging blocks --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index bd93c4749..87e3e968f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,3 +9,6 @@ exclude_lines = # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: + # Don't complain about debug code + if Image.DEBUG: + if DEBUG: \ No newline at end of file From 3834d01044c360d58fb4a1939006089b9f724b26 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 12:07:53 -0700 Subject: [PATCH 243/488] Help for testing --- Makefile | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Makefile b/Makefile index 4f6a00711..0637e901f 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ + + pre: virtualenv . bin/pip install -r requirements.txt @@ -9,3 +11,32 @@ pre: check-manifest pyroma . viewdoc + +clean: + python setup.py clean + rm PIL/*.so || true + find . -name __pycache__ | xargs rm -r + +install: + python setup.py install + python selftest.py --installed + +test: install + python test-installed.py + +inplace: clean + python setup.py build_ext --inplace + +coverage: +# requires nose-cov + coverage erase + coverage run --parallel-mode --include=PIL/* selftest.py + nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py +# doesn't combine properly before report, +# writing report instead of displaying invalid report + rm -r htmlcov || true + coverage combine + coverage report + +test-dep: + pip install coveralls nose nose-cov pep8 pyflakes From 767182a56f086b0b50dbec01c311e0c9c876fced Mon Sep 17 00:00:00 2001 From: cgohlke Date: Mon, 30 Jun 2014 15:26:41 -0700 Subject: [PATCH 244/488] ENH: enable inline functions by default --- libImaging/ImPlatform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/ImPlatform.h b/libImaging/ImPlatform.h index 8e85e627c..be1f20f3f 100644 --- a/libImaging/ImPlatform.h +++ b/libImaging/ImPlatform.h @@ -17,7 +17,7 @@ #error Sorry, this library requires ANSI header files. #endif -#if !defined(PIL_USE_INLINE) +#if defined(PIL_NO_INLINE) #define inline #else #if defined(_MSC_VER) && !defined(__GNUC__) From 2faf36ab5b9104c1088d973c9f882e64a826ba09 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 30 Jun 2014 15:30:05 -0700 Subject: [PATCH 245/488] Not enabling multithreaded tests on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 800fac43a..d4880847c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python notifications: irc: "chat.freenode.net#pil" -env: MAX_CONCURRENCY=4 NOSE_PROCESSES=4 NOSE_PROCESS_TIMEOUT=30 +env: MAX_CONCURRENCY=4 python: - "pypy" From 9dd559cb50e20a23a5d046c600572d5e850176e5 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 30 Jun 2014 15:35:24 -0700 Subject: [PATCH 246/488] Enable multithread testing for travis --- .travis.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d4880847c..3a8d8653f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python notifications: irc: "chat.freenode.net#pil" -env: MAX_CONCURRENCY=4 +env: MAX_CONCURRENCY=4 NOSE_PROCESSES=4 NOSE_PROCESS_TIMEOUT=30 python: - "pypy" @@ -14,9 +14,9 @@ python: - 3.4 install: - - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake" + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls nose pyroma" + - "pip install coveralls nose nose-cov" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp @@ -35,10 +35,13 @@ script: - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --parallel-mode --include=PIL/* selftest.py; fi + # write html report, then ignore. Coverage needs to be combined first + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py; fi after_success: + - ls -l .coverage* + - coverage combine - coverage report - coveralls - pip install pep8 pyflakes From 7b838f31b48d9e0fc59a5ffc19fff12105863322 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 02:05:55 +0300 Subject: [PATCH 247/488] Import helper so unittest2 is imported for Py2.6 (re: #743) --- Tests/test_pyroma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 2c2aeae96..c10156cc0 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,4 +1,4 @@ -import unittest +from helper import * try: import pyroma From e220e597bfd46b1a8b3cc789b8153860fa5c4616 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 02:16:34 +0300 Subject: [PATCH 248/488] Separate install and report scripts for diff-cover --- .travis.yml | 4 ++-- Scripts/diffcover-install.sh | 7 +++++++ Scripts/{diffcover.sh => diffcover-report.sh} | 1 - 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100755 Scripts/diffcover-install.sh rename Scripts/{diffcover.sh => diffcover-report.sh} (66%) diff --git a/.travis.yml b/.travis.yml index 4f15d19d8..50bfc3294 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,5 +51,5 @@ after_success: # Coverage and quality reports on just the latest diff. # (Installation is very slow on Py3, so just do it for Py2.) - - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then git fetch origin master:refs/remotes/origin/master; fi - - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover.sh; fi + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-report.sh; fi diff --git a/Scripts/diffcover-install.sh b/Scripts/diffcover-install.sh new file mode 100755 index 000000000..93e06efe3 --- /dev/null +++ b/Scripts/diffcover-install.sh @@ -0,0 +1,7 @@ +# Fetch the remote master branch before running diff-cover on Travis CI. +# https://github.com/edx/diff-cover#troubleshooting +git fetch origin master:refs/remotes/origin/master + +# CFLAGS=-O0 means build with no optimisation. +# Makes build much quicker for lxml and other dependencies. +time CFLAGS=-O0 pip install --use-wheel diff_cover diff --git a/Scripts/diffcover.sh b/Scripts/diffcover-report.sh similarity index 66% rename from Scripts/diffcover.sh rename to Scripts/diffcover-report.sh index a573c97d4..dacc8b42f 100755 --- a/Scripts/diffcover.sh +++ b/Scripts/diffcover-report.sh @@ -1,4 +1,3 @@ -time CFLAGS=-O0 pip install --use-wheel diff_cover coverage xml diff-cover coverage.xml diff-quality --violation=pep8 From c0fb5ace23e166a8610103106b8c58224140a527 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 30 Jun 2014 19:52:07 -0400 Subject: [PATCH 249/488] Fix load_djpeg and _save_cjpeg in windows --- PIL/JpegImagePlugin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 1e5d57e42..a434c5581 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -360,8 +360,7 @@ class JpegImageFile(ImageFile.ImageFile): f, path = tempfile.mkstemp() os.close(f) if os.path.exists(self.filename): - with open(path, 'wb') as f: - subprocess.check_call(["djpeg", self.filename], stdout=f) + subprocess.check_call(["djpeg", "-outfile", path, self.filename]) else: raise ValueError("Invalid Filename") @@ -606,8 +605,7 @@ def _save_cjpeg(im, fp, filename): import os import subprocess tempfile = im._dump() - with open(filename, 'wb') as f: - subprocess.check_call(["cjpeg", tempfile], stdout=f) + subprocess.check_call(["cjpeg", "-outfile", filename, tempfile]) try: os.unlink(file) except: From fb27befa1a344b906d45874167f59a6004767bdd Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 30 Jun 2014 22:07:44 -0400 Subject: [PATCH 250/488] Skip shell injection tests for Windows --- Tests/test_shell_injection.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index fd14e5f44..eff03fd59 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, tearDownModule from helper import djpeg_available, cjpeg_available, netpbm_available +import sys import shutil from PIL import Image, JpegImagePlugin, GifImagePlugin @@ -16,6 +17,7 @@ test_filenames = ( "temp_'\"&&", ) +@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS") class TestShellInjection(PillowTestCase): def assert_save_filename_check(self, src_img, save_func): From 10da645ac676f238a97d8d177c8338b248800a3e Mon Sep 17 00:00:00 2001 From: cgohlke Date: Mon, 30 Jun 2014 19:19:09 -0700 Subject: [PATCH 251/488] Skip LargeMemoryTest on 32 bit systems --- Tests/large_memory_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 8552ed4dd..77b4d9f22 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -1,3 +1,6 @@ +import sys +import unittest + from helper import * # This test is not run automatically. @@ -14,6 +17,7 @@ YDIM = 32769 XDIM = 48000 +@unittest.skipIf(sys.maxsize <= 2**32, "requires 64 bit system") class LargeMemoryTest(PillowTestCase): def _write_png(self, xdim, ydim): From 647f2e177193e0748d22cd16a331496135b56766 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Mon, 30 Jun 2014 19:20:15 -0700 Subject: [PATCH 252/488] Skip LargeMemoryNumpyTest on 32 bit systems --- Tests/large_memory_numpy_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index deb2fc8c6..1701f995b 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -1,3 +1,6 @@ +import sys +import unittest + from helper import * # This test is not run automatically. @@ -18,6 +21,7 @@ YDIM = 32769 XDIM = 48000 +@unittest.skipIf(sys.maxsize <= 2**32, "requires 64 bit system") class LargeMemoryNumpyTest(PillowTestCase): def _write_png(self, xdim, ydim): From f0342393fffa6ffe82cc16974ae0947099c1533c Mon Sep 17 00:00:00 2001 From: cgohlke Date: Mon, 30 Jun 2014 23:20:12 -0700 Subject: [PATCH 253/488] Don't import unittest because it's done in helper --- Tests/large_memory_numpy_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/large_memory_numpy_test.py b/Tests/large_memory_numpy_test.py index 1701f995b..8a13d0aea 100644 --- a/Tests/large_memory_numpy_test.py +++ b/Tests/large_memory_numpy_test.py @@ -1,5 +1,4 @@ import sys -import unittest from helper import * From 5c3736c4a60a00141b0cedecfd9643e00f35f37f Mon Sep 17 00:00:00 2001 From: cgohlke Date: Mon, 30 Jun 2014 23:20:39 -0700 Subject: [PATCH 254/488] Don't import unittest because it's done in helper --- Tests/large_memory_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py index 77b4d9f22..a63a42cd5 100644 --- a/Tests/large_memory_test.py +++ b/Tests/large_memory_test.py @@ -1,5 +1,4 @@ import sys -import unittest from helper import * From 1afa2f2aa9a0da3dd535299dff682327893713a4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 10:44:36 +0300 Subject: [PATCH 255/488] some flake8 --- PIL/PalmImagePlugin.py | 163 ++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 74 deletions(-) diff --git a/PIL/PalmImagePlugin.py b/PIL/PalmImagePlugin.py index 203a6d9f6..5f4c08490 100644 --- a/PIL/PalmImagePlugin.py +++ b/PIL/PalmImagePlugin.py @@ -12,74 +12,75 @@ __version__ = "1.0" from PIL import Image, ImageFile, _binary _Palm8BitColormapValues = ( - ( 255, 255, 255 ), ( 255, 204, 255 ), ( 255, 153, 255 ), ( 255, 102, 255 ), - ( 255, 51, 255 ), ( 255, 0, 255 ), ( 255, 255, 204 ), ( 255, 204, 204 ), - ( 255, 153, 204 ), ( 255, 102, 204 ), ( 255, 51, 204 ), ( 255, 0, 204 ), - ( 255, 255, 153 ), ( 255, 204, 153 ), ( 255, 153, 153 ), ( 255, 102, 153 ), - ( 255, 51, 153 ), ( 255, 0, 153 ), ( 204, 255, 255 ), ( 204, 204, 255 ), - ( 204, 153, 255 ), ( 204, 102, 255 ), ( 204, 51, 255 ), ( 204, 0, 255 ), - ( 204, 255, 204 ), ( 204, 204, 204 ), ( 204, 153, 204 ), ( 204, 102, 204 ), - ( 204, 51, 204 ), ( 204, 0, 204 ), ( 204, 255, 153 ), ( 204, 204, 153 ), - ( 204, 153, 153 ), ( 204, 102, 153 ), ( 204, 51, 153 ), ( 204, 0, 153 ), - ( 153, 255, 255 ), ( 153, 204, 255 ), ( 153, 153, 255 ), ( 153, 102, 255 ), - ( 153, 51, 255 ), ( 153, 0, 255 ), ( 153, 255, 204 ), ( 153, 204, 204 ), - ( 153, 153, 204 ), ( 153, 102, 204 ), ( 153, 51, 204 ), ( 153, 0, 204 ), - ( 153, 255, 153 ), ( 153, 204, 153 ), ( 153, 153, 153 ), ( 153, 102, 153 ), - ( 153, 51, 153 ), ( 153, 0, 153 ), ( 102, 255, 255 ), ( 102, 204, 255 ), - ( 102, 153, 255 ), ( 102, 102, 255 ), ( 102, 51, 255 ), ( 102, 0, 255 ), - ( 102, 255, 204 ), ( 102, 204, 204 ), ( 102, 153, 204 ), ( 102, 102, 204 ), - ( 102, 51, 204 ), ( 102, 0, 204 ), ( 102, 255, 153 ), ( 102, 204, 153 ), - ( 102, 153, 153 ), ( 102, 102, 153 ), ( 102, 51, 153 ), ( 102, 0, 153 ), - ( 51, 255, 255 ), ( 51, 204, 255 ), ( 51, 153, 255 ), ( 51, 102, 255 ), - ( 51, 51, 255 ), ( 51, 0, 255 ), ( 51, 255, 204 ), ( 51, 204, 204 ), - ( 51, 153, 204 ), ( 51, 102, 204 ), ( 51, 51, 204 ), ( 51, 0, 204 ), - ( 51, 255, 153 ), ( 51, 204, 153 ), ( 51, 153, 153 ), ( 51, 102, 153 ), - ( 51, 51, 153 ), ( 51, 0, 153 ), ( 0, 255, 255 ), ( 0, 204, 255 ), - ( 0, 153, 255 ), ( 0, 102, 255 ), ( 0, 51, 255 ), ( 0, 0, 255 ), - ( 0, 255, 204 ), ( 0, 204, 204 ), ( 0, 153, 204 ), ( 0, 102, 204 ), - ( 0, 51, 204 ), ( 0, 0, 204 ), ( 0, 255, 153 ), ( 0, 204, 153 ), - ( 0, 153, 153 ), ( 0, 102, 153 ), ( 0, 51, 153 ), ( 0, 0, 153 ), - ( 255, 255, 102 ), ( 255, 204, 102 ), ( 255, 153, 102 ), ( 255, 102, 102 ), - ( 255, 51, 102 ), ( 255, 0, 102 ), ( 255, 255, 51 ), ( 255, 204, 51 ), - ( 255, 153, 51 ), ( 255, 102, 51 ), ( 255, 51, 51 ), ( 255, 0, 51 ), - ( 255, 255, 0 ), ( 255, 204, 0 ), ( 255, 153, 0 ), ( 255, 102, 0 ), - ( 255, 51, 0 ), ( 255, 0, 0 ), ( 204, 255, 102 ), ( 204, 204, 102 ), - ( 204, 153, 102 ), ( 204, 102, 102 ), ( 204, 51, 102 ), ( 204, 0, 102 ), - ( 204, 255, 51 ), ( 204, 204, 51 ), ( 204, 153, 51 ), ( 204, 102, 51 ), - ( 204, 51, 51 ), ( 204, 0, 51 ), ( 204, 255, 0 ), ( 204, 204, 0 ), - ( 204, 153, 0 ), ( 204, 102, 0 ), ( 204, 51, 0 ), ( 204, 0, 0 ), - ( 153, 255, 102 ), ( 153, 204, 102 ), ( 153, 153, 102 ), ( 153, 102, 102 ), - ( 153, 51, 102 ), ( 153, 0, 102 ), ( 153, 255, 51 ), ( 153, 204, 51 ), - ( 153, 153, 51 ), ( 153, 102, 51 ), ( 153, 51, 51 ), ( 153, 0, 51 ), - ( 153, 255, 0 ), ( 153, 204, 0 ), ( 153, 153, 0 ), ( 153, 102, 0 ), - ( 153, 51, 0 ), ( 153, 0, 0 ), ( 102, 255, 102 ), ( 102, 204, 102 ), - ( 102, 153, 102 ), ( 102, 102, 102 ), ( 102, 51, 102 ), ( 102, 0, 102 ), - ( 102, 255, 51 ), ( 102, 204, 51 ), ( 102, 153, 51 ), ( 102, 102, 51 ), - ( 102, 51, 51 ), ( 102, 0, 51 ), ( 102, 255, 0 ), ( 102, 204, 0 ), - ( 102, 153, 0 ), ( 102, 102, 0 ), ( 102, 51, 0 ), ( 102, 0, 0 ), - ( 51, 255, 102 ), ( 51, 204, 102 ), ( 51, 153, 102 ), ( 51, 102, 102 ), - ( 51, 51, 102 ), ( 51, 0, 102 ), ( 51, 255, 51 ), ( 51, 204, 51 ), - ( 51, 153, 51 ), ( 51, 102, 51 ), ( 51, 51, 51 ), ( 51, 0, 51 ), - ( 51, 255, 0 ), ( 51, 204, 0 ), ( 51, 153, 0 ), ( 51, 102, 0 ), - ( 51, 51, 0 ), ( 51, 0, 0 ), ( 0, 255, 102 ), ( 0, 204, 102 ), - ( 0, 153, 102 ), ( 0, 102, 102 ), ( 0, 51, 102 ), ( 0, 0, 102 ), - ( 0, 255, 51 ), ( 0, 204, 51 ), ( 0, 153, 51 ), ( 0, 102, 51 ), - ( 0, 51, 51 ), ( 0, 0, 51 ), ( 0, 255, 0 ), ( 0, 204, 0 ), - ( 0, 153, 0 ), ( 0, 102, 0 ), ( 0, 51, 0 ), ( 17, 17, 17 ), - ( 34, 34, 34 ), ( 68, 68, 68 ), ( 85, 85, 85 ), ( 119, 119, 119 ), - ( 136, 136, 136 ), ( 170, 170, 170 ), ( 187, 187, 187 ), ( 221, 221, 221 ), - ( 238, 238, 238 ), ( 192, 192, 192 ), ( 128, 0, 0 ), ( 128, 0, 128 ), - ( 0, 128, 0 ), ( 0, 128, 128 ), ( 0, 0, 0 ), ( 0, 0, 0 ), - ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), - ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), - ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), - ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), - ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), - ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 ), ( 0, 0, 0 )) + (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255), + (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204), + (255, 153, 204), (255, 102, 204), (255, 51, 204), (255, 0, 204), + (255, 255, 153), (255, 204, 153), (255, 153, 153), (255, 102, 153), + (255, 51, 153), (255, 0, 153), (204, 255, 255), (204, 204, 255), + (204, 153, 255), (204, 102, 255), (204, 51, 255), (204, 0, 255), + (204, 255, 204), (204, 204, 204), (204, 153, 204), (204, 102, 204), + (204, 51, 204), (204, 0, 204), (204, 255, 153), (204, 204, 153), + (204, 153, 153), (204, 102, 153), (204, 51, 153), (204, 0, 153), + (153, 255, 255), (153, 204, 255), (153, 153, 255), (153, 102, 255), + (153, 51, 255), (153, 0, 255), (153, 255, 204), (153, 204, 204), + (153, 153, 204), (153, 102, 204), (153, 51, 204), (153, 0, 204), + (153, 255, 153), (153, 204, 153), (153, 153, 153), (153, 102, 153), + (153, 51, 153), (153, 0, 153), (102, 255, 255), (102, 204, 255), + (102, 153, 255), (102, 102, 255), (102, 51, 255), (102, 0, 255), + (102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204), + (102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153), + (102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153), + ( 51, 255, 255), ( 51, 204, 255), ( 51, 153, 255), ( 51, 102, 255), + ( 51, 51, 255), ( 51, 0, 255), ( 51, 255, 204), ( 51, 204, 204), + ( 51, 153, 204), ( 51, 102, 204), ( 51, 51, 204), ( 51, 0, 204), + ( 51, 255, 153), ( 51, 204, 153), ( 51, 153, 153), ( 51, 102, 153), + ( 51, 51, 153), ( 51, 0, 153), ( 0, 255, 255), ( 0, 204, 255), + ( 0, 153, 255), ( 0, 102, 255), ( 0, 51, 255), ( 0, 0, 255), + ( 0, 255, 204), ( 0, 204, 204), ( 0, 153, 204), ( 0, 102, 204), + ( 0, 51, 204), ( 0, 0, 204), ( 0, 255, 153), ( 0, 204, 153), + ( 0, 153, 153), ( 0, 102, 153), ( 0, 51, 153), ( 0, 0, 153), + (255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102), + (255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51), + (255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51), + (255, 255, 0), (255, 204, 0), (255, 153, 0), (255, 102, 0), + (255, 51, 0), (255, 0, 0), (204, 255, 102), (204, 204, 102), + (204, 153, 102), (204, 102, 102), (204, 51, 102), (204, 0, 102), + (204, 255, 51), (204, 204, 51), (204, 153, 51), (204, 102, 51), + (204, 51, 51), (204, 0, 51), (204, 255, 0), (204, 204, 0), + (204, 153, 0), (204, 102, 0), (204, 51, 0), (204, 0, 0), + (153, 255, 102), (153, 204, 102), (153, 153, 102), (153, 102, 102), + (153, 51, 102), (153, 0, 102), (153, 255, 51), (153, 204, 51), + (153, 153, 51), (153, 102, 51), (153, 51, 51), (153, 0, 51), + (153, 255, 0), (153, 204, 0), (153, 153, 0), (153, 102, 0), + (153, 51, 0), (153, 0, 0), (102, 255, 102), (102, 204, 102), + (102, 153, 102), (102, 102, 102), (102, 51, 102), (102, 0, 102), + (102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51), + (102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0), + (102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0), + ( 51, 255, 102), ( 51, 204, 102), ( 51, 153, 102), ( 51, 102, 102), + ( 51, 51, 102), ( 51, 0, 102), ( 51, 255, 51), ( 51, 204, 51), + ( 51, 153, 51), ( 51, 102, 51), ( 51, 51, 51), ( 51, 0, 51), + ( 51, 255, 0), ( 51, 204, 0), ( 51, 153, 0), ( 51, 102, 0), + ( 51, 51, 0), ( 51, 0, 0), ( 0, 255, 102), ( 0, 204, 102), + ( 0, 153, 102), ( 0, 102, 102), ( 0, 51, 102), ( 0, 0, 102), + ( 0, 255, 51), ( 0, 204, 51), ( 0, 153, 51), ( 0, 102, 51), + ( 0, 51, 51), ( 0, 0, 51), ( 0, 255, 0), ( 0, 204, 0), + ( 0, 153, 0), ( 0, 102, 0), ( 0, 51, 0), ( 17, 17, 17), + ( 34, 34, 34), ( 68, 68, 68), ( 85, 85, 85), (119, 119, 119), + (136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221), + (238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128), + ( 0, 128, 0), ( 0, 128, 128), ( 0, 0, 0), ( 0, 0, 0), + ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), + ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), + ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), + ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), + ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), + ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0)) + # so build a prototype image to be used for palette resampling def build_prototype_image(): - image = Image.new("L", (1,len(_Palm8BitColormapValues),)) + image = Image.new("L", (1, len(_Palm8BitColormapValues),)) image.putdata(list(range(len(_Palm8BitColormapValues)))) palettedata = () for i in range(len(_Palm8BitColormapValues)): @@ -91,7 +92,8 @@ def build_prototype_image(): Palm8BitColormapImage = build_prototype_image() -# OK, we now have in Palm8BitColormapImage, a "P"-mode image with the right palette +# OK, we now have in Palm8BitColormapImage, +# a "P"-mode image with the right palette # # -------------------------------------------------------------------- @@ -110,6 +112,7 @@ _COMPRESSION_TYPES = { o8 = _binary.o8 o16b = _binary.o16be + # # -------------------------------------------------------------------- @@ -127,12 +130,16 @@ def _save(im, fp, filename, check=0): bpp = 8 version = 1 - elif im.mode == "L" and "bpp" in im.encoderinfo and im.encoderinfo["bpp"] in (1, 2, 4): + elif (im.mode == "L" and + "bpp" in im.encoderinfo and + im.encoderinfo["bpp"] in (1, 2, 4)): - # this is 8-bit grayscale, so we shift it to get the high-order bits, and invert it because + # this is 8-bit grayscale, so we shift it to get the high-order bits, + # and invert it because # Palm does greyscale from white (0) to black (1) bpp = im.encoderinfo["bpp"] - im = im.point(lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift)) + im = im.point( + lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift)) # we ignore the palette here im.mode = "P" rawmode = "P;" + str(bpp) @@ -140,8 +147,9 @@ def _save(im, fp, filename, check=0): elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4): - # here we assume that even though the inherent mode is 8-bit grayscale, only - # the lower bpp bits are significant. We invert them to match the Palm. + # here we assume that even though the inherent mode is 8-bit grayscale, + # only the lower bpp bits are significant. + # We invert them to match the Palm. bpp = im.info["bpp"] im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval)) # we ignore the palette here @@ -205,12 +213,19 @@ def _save(im, fp, filename, check=0): for i in range(256): fp.write(o8(i)) if colormapmode == 'RGB': - fp.write(o8(colormap[3 * i]) + o8(colormap[3 * i + 1]) + o8(colormap[3 * i + 2])) + fp.write( + o8(colormap[3 * i]) + + o8(colormap[3 * i + 1]) + + o8(colormap[3 * i + 2])) elif colormapmode == 'RGBA': - fp.write(o8(colormap[4 * i]) + o8(colormap[4 * i + 1]) + o8(colormap[4 * i + 2])) + fp.write( + o8(colormap[4 * i]) + + o8(colormap[4 * i + 1]) + + o8(colormap[4 * i + 2])) # now convert data to raw form - ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, rowbytes, 1))]) + ImageFile._save( + im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))]) fp.flush() From dae96f37629d41001498ece79b0e2eed7e1690ae Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 06:35:51 -0400 Subject: [PATCH 256/488] No comma --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0831a6b81..0da5b77ed 100644 --- a/README.rst +++ b/README.rst @@ -20,4 +20,4 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master :target: https://coveralls.io/r/python-pillow/Pillow?branch=master -The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more. +The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details and more. From 492be8ba3e751d3b042841c32584fbdd27e1c1b6 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 06:50:35 -0400 Subject: [PATCH 257/488] More concise --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 0da5b77ed..d3e736508 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Pillow *Python Imaging Library (Fork)* -Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please read the `documentation `_. .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master :target: https://travis-ci.org/python-pillow/Pillow @@ -20,4 +20,3 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master :target: https://coveralls.io/r/python-pillow/Pillow?branch=master -The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details and more. From d353e391e12e88ea90d018039449be43c213a256 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:02:03 -0400 Subject: [PATCH 258/488] Clean up Add myself to early changelog entries ; more detail about ``import Image`` removal ; other nits --- CHANGES.rst | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ea5f56b95..a5cf10c02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -495,6 +495,10 @@ Changelog (Pillow) 2.0.0 (2013-03-15) ------------------ +.. Note:: Special thanks to Christoph Gohlke and Eric Soroos for assisting with a pre-PyCon 2013 release! + +- Many other bug fixes and enhancements by many other people. + - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) [fluggo] @@ -518,10 +522,6 @@ Changelog (Pillow) - Added support for PNG images with transparency palette. [d-schmidt] -- Many other bug fixes and enhancements by many other people (see commit log and/or docs/CONTRIBUTORS.txt). - -- Special thanks to Christoph Gohlke and Eric Soroos for rallying around the effort to get a release out for PyCon 2013. - 1.7.8 (2012-11-01) ------------------ @@ -594,44 +594,55 @@ Changelog (Pillow) [elro] - Doc fixes + [aclark] 1.5 (11/28/2010) ---------------- - Module and package fixes + [aclark] 1.4 (11/28/2010) ---------------- - Doc fixes + [aclark] 1.3 (11/28/2010) ---------------- - Add support for /lib64 and /usr/lib64 library directories on Linux + [aclark] + - Doc fixes + [aclark] 1.2 (08/02/2010) ---------------- -- On OS X also check for freetype2 in the X11 path [jezdez] -- Doc fixes [aclark] +- On OS X also check for freetype2 in the X11 path + [jezdez] + +- Doc fixes + [aclark] 1.1 (07/31/2010) ---------------- - Removed setuptools_hg requirement + [aclark] + - Doc fixes + [aclark] 1.0 (07/30/2010) ---------------- -- Forked PIL based on Hanno Schlichting's re-packaging - (http://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz) +- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required. +- Forked PIL based on `Hanno Schlichting's re-packaging `_ + [aclark] -- Remove support for importing from the standard namespace - -.. Note:: What follows is the original PIL 1.1.7 CHANGES file contents +.. Note:: What follows is the original PIL 1.1.7 CHANGES :: From 0e5cf2b6e586970d1eeb7b06971d385a86bc6c20 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:16:25 -0400 Subject: [PATCH 259/488] Bigger click --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d3e736508..224f98f03 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Pillow *Python Imaging Library (Fork)* -Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please read the `documentation `_. +Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation `_. .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master :target: https://travis-ci.org/python-pillow/Pillow From acde9de4ca5e7bbb543e146bc69af565cdd01570 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:23:19 -0400 Subject: [PATCH 260/488] Add developer docs [ci skip] Document our various development practices, include a reminder to myself not to run the tests with every commit! --- docs/developer.rst | 4 ++++ docs/index.rst | 1 + 2 files changed, 5 insertions(+) create mode 100644 docs/developer.rst diff --git a/docs/developer.rst b/docs/developer.rst new file mode 100644 index 000000000..5e34934f7 --- /dev/null +++ b/docs/developer.rst @@ -0,0 +1,4 @@ +Developer +========= + +.. Note:: When committing only trivial changes, please include [ci skip] in the commit message to avoid running tests on Travis-CI. Thank you! diff --git a/docs/index.rst b/docs/index.rst index a8c204228..1a7e81550 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ and old versions from `PyPI `_. guides reference/index.rst handbook/appendices + developer original-readme Support Pillow! From 176987f8aa009563cf40f9677776eb173c281478 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:39:03 -0400 Subject: [PATCH 261/488] Rename VERSION -> PILLOW_VERSION Provide consistency with version variables elsewhere in package. --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 9341b93bb..e4ddb2c2f 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -VERSION = '2.4.0' +PILLOW_VERSION = '2.4.0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None @@ -622,7 +622,7 @@ class pil_build_ext(build_ext): print("-" * 68) print("PIL SETUP SUMMARY") print("-" * 68) - print("version Pillow %s" % VERSION) + print("version Pillow %s" % PILLOW_VERSION) v = sys.version.split("[") print("platform %s %s" % (sys.platform, v[0].strip())) for v in v[1:]: @@ -718,7 +718,7 @@ class pil_build_ext(build_ext): setup( name=NAME, - version=VERSION, + version=PILLOW_VERSION, description='Python Imaging Library (Fork)', long_description=( _read('README.rst') + b'\n' + From c3d9f1b4b95f9830d42477e3c48012be15f33a1e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:42:29 -0400 Subject: [PATCH 262/488] Document version number location [ci skip] --- docs/developer.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/developer.rst b/docs/developer.rst index 5e34934f7..ea3b0f05e 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -2,3 +2,16 @@ Developer ========= .. Note:: When committing only trivial changes, please include [ci skip] in the commit message to avoid running tests on Travis-CI. Thank you! + + +Release +------- + +Details about making a Pillow release. + +Version number +~~~~~~~~~~~~~~ + +The version number is currently stored in 3 places:: + + PIL/__init__.py _imaging.c setup.py From f8b6163d9f7a8e785db93f221763fc5d2650104d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:42:50 -0400 Subject: [PATCH 263/488] Bump 2.4.0 -> 2.5.0 --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 14daee5ca..d446aa19b 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.4.0' # Pillow +PILLOW_VERSION = '2.5.0' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 39b21f23e..92258032f 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.4.0" +#define PILLOW_VERSION "2.5.0" #include "Python.h" diff --git a/setup.py b/setup.py index e4ddb2c2f..e94e34d28 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.4.0' +PILLOW_VERSION = '2.5.0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From a47b8c15da6a6fa954f7329024df8ea9ce689763 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 07:44:39 -0400 Subject: [PATCH 264/488] Move developer to guides section [ci skip] --- docs/guides.rst | 1 + docs/index.rst | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides.rst b/docs/guides.rst index 926d3cfcc..87ce75bd4 100644 --- a/docs/guides.rst +++ b/docs/guides.rst @@ -8,3 +8,4 @@ Guides handbook/tutorial handbook/concepts porting-pil-to-pillow + developer diff --git a/docs/index.rst b/docs/index.rst index 1a7e81550..a8c204228 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,7 +35,6 @@ and old versions from `PyPI `_. guides reference/index.rst handbook/appendices - developer original-readme Support Pillow! From 443cc908ae3852a077d437a2fb5fab2f4cdca06a Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 16:25:24 +0300 Subject: [PATCH 265/488] Show pyflakes quality violations (which can be programming errors) before pep8 (which is just style). --- .travis.yml | 2 +- Scripts/{diffcover-report.sh => diffcover-run.sh} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Scripts/{diffcover-report.sh => diffcover-run.sh} (100%) diff --git a/.travis.yml b/.travis.yml index 50bfc3294..6ada5fc55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,4 +52,4 @@ after_success: # Coverage and quality reports on just the latest diff. # (Installation is very slow on Py3, so just do it for Py2.) - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi - - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-report.sh; fi + - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi diff --git a/Scripts/diffcover-report.sh b/Scripts/diffcover-run.sh similarity index 100% rename from Scripts/diffcover-report.sh rename to Scripts/diffcover-run.sh index dacc8b42f..02efab6ae 100755 --- a/Scripts/diffcover-report.sh +++ b/Scripts/diffcover-run.sh @@ -1,4 +1,4 @@ coverage xml diff-cover coverage.xml -diff-quality --violation=pep8 diff-quality --violation=pyflakes +diff-quality --violation=pep8 From e3f9fa0d781fe341153a1ab33ad19d891ab2e710 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 09:07:18 -0700 Subject: [PATCH 266/488] Skip known failing test --- Tests/test_imagedraw.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index b9bedf33e..4610e2b0b 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -317,6 +317,9 @@ class TestImageDraw(PillowTestCase): 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)) From 2ca9ffba5d0981e5fbbe53dba297a0abf0013272 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 09:09:00 -0700 Subject: [PATCH 267/488] renabling pyroma --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3a8d8653f..57c2cb94b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls nose nose-cov" + - "pip install coveralls nose pyroma nose-cov" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp From a10b91786a3c1e0e9355b79ccb2220a7b2dd8874 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 10:05:38 -0700 Subject: [PATCH 268/488] Fix compilation errors with C90 standard --- libImaging/Draw.c | 48 ++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 2f53fde79..307bb4425 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -421,19 +421,24 @@ static inline int polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline) { + + Edge** edge_table; + float* xx; + int edge_count = 0; + int ymin = im->ysize - 1; + int ymax = 0; + int i; + if (n <= 0) { return 0; } /* Initialize the edge table and find polygon boundaries */ - Edge** edge_table = malloc(sizeof(Edge*) * n); + edge_table = malloc(sizeof(Edge*) * n); if (!edge_table) { return -1; } - int edge_count = 0; - int ymin = im->ysize - 1; - int ymax = 0; - int i; + 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 */ @@ -457,7 +462,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, } /* Process the edge table with a scan line searching for intersections */ - float* xx = malloc(sizeof(float) * edge_count * 2); + xx = malloc(sizeof(float) * edge_count * 2); if (!xx) { free(edge_table); return -1; @@ -591,6 +596,11 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, { DRAW* draw; INT32 ink; + int dx, dy; + double big_hypotenuse, small_hypotenuse, ratio_max, ratio_min; + int dxmin, dxmax, dymin, dymax; + Edge e[4]; + int vertices[4][2]; DRAWINIT(); @@ -599,35 +609,35 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, return 0; } - int dx = x1-x0; - int dy = y1-y0; + dx = x1-x0; + dy = y1-y0; if (dx == 0 && dy == 0) { draw->point(im, x0, y0, ink); return 0; } - double big_hypotenuse = sqrt((double) (dx*dx + dy*dy)); - double small_hypotenuse = (width - 1) / 2.0; - double ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse; - double ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse; + 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; - int dxmin = ROUND_DOWN(ratio_min * dy); - int dxmax = ROUND_DOWN(ratio_max * dy); - int dymin = ROUND_DOWN(ratio_min * dx); - int dymax = ROUND_DOWN(ratio_max * dx); - int vertices[4][2] = { + dxmin = ROUND_DOWN(ratio_min * dy); + dxmax = ROUND_DOWN(ratio_max * dy); + dymin = ROUND_DOWN(ratio_min * dx); + dymax = ROUND_DOWN(ratio_max * dx); + + vertices = (int[][]) { {x0 - dxmin, y0 + dymax}, {x1 - dxmin, y1 + dymax}, {x1 + dxmax, y1 - dymin}, {x0 + dxmax, y0 - dymin} }; - Edge e[4]; 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; From 98a49917629702c882575fa34985bb00f545b032 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 10:20:15 -0700 Subject: [PATCH 269/488] Using local block rather than array literal --- libImaging/Draw.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 307bb4425..2ff03e049 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -625,21 +625,21 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, dxmax = ROUND_DOWN(ratio_max * dy); dymin = ROUND_DOWN(ratio_min * dx); dymax = ROUND_DOWN(ratio_max * dx); - - vertices = (int[][]) { - {x0 - dxmin, y0 + dymax}, - {x1 - dxmin, y1 + dymax}, - {x1 + dxmax, y1 - dymin}, - {x0 + dxmax, y0 - dymin} - }; + { + int vertices[4][2] = { + {x0 - dxmin, y0 + dymax}, + {x1 - dxmin, y1 + dymax}, + {x1 + dxmax, y1 - dymin}, + {x0 + dxmax, y0 - dymin} + }; - 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); + 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; } From cec5fd9d38aac5534e7e3b0078cbed98a4a6c0c6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 11:09:20 -0700 Subject: [PATCH 270/488] f doesn't exist, BytesIO objects have fileno(), but may return OsError --- PIL/Jpeg2KImagePlugin.py | 20 +++++++++----------- Tests/test_file_jpeg2k.py | 8 ++++++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 66069802e..0a7a6e297 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -172,18 +172,16 @@ class Jpeg2KImageFile(ImageFile.ImageFile): fd = -1 length = -1 - if hasattr(self.fp, "fileno"): + try: + fd = self.fp.fileno() + length = os.fstat(fd).st_size + except: + fd = -1 try: - fd = self.fp.fileno() - length = os.fstat(fd).st_size - except: - fd = -1 - elif hasattr(self.fp, "seek"): - try: - pos = f.tell() - f.seek(0, 2) - length = f.tell() - f.seek(pos, 0) + pos = self.fp.tell() + self.fp.seek(0, 2) + length = self.fp.tell() + self.fp.seek(pos, 0) except: length = -1 diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b763687f7..e115df3a8 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -39,6 +39,14 @@ class TestFileJpeg2k(PillowTestCase): self.assertEqual(im.size, (640, 480)) self.assertEqual(im.format, 'JPEG2000') + def test_bytesio(self): + with open('Tests/images/test-card-lossless.jp2', 'rb') as f: + data = BytesIO(f.read()) + im = Image.open(data) + im.load() + print ("bytesio") + self.assert_image_similar(im, test_card, 1.0e-3) + # These two test pre-written JPEG 2000 files that were not written with # PIL (they were made using Adobe Photoshop) From 62c8ae1254d2f6a34cfe4cb9b8c767759912a8be Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 14:17:07 -0400 Subject: [PATCH 271/488] Update --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a5cf10c02..f19ae0683 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Imagedraw rewrite + [terseus, wiredfool] + +- Add support for multithreaded test execution + [wiredfool] + - Prevent shell injection #748 [mbrown1413, wiredfool] From b53184aa30ca7dd96d2b03a9442a4db0522b8665 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 11:52:52 -0700 Subject: [PATCH 272/488] Reverting pr#755 changes to .travis.yml --- .travis.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57c2cb94b..d4880847c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: python notifications: irc: "chat.freenode.net#pil" -env: MAX_CONCURRENCY=4 NOSE_PROCESSES=4 NOSE_PROCESS_TIMEOUT=30 +env: MAX_CONCURRENCY=4 python: - "pypy" @@ -14,9 +14,9 @@ python: - 3.4 install: - - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake" - "pip install cffi" - - "pip install coveralls nose pyroma nose-cov" + - "pip install coveralls nose pyroma" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp @@ -35,13 +35,10 @@ script: - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --parallel-mode --include=PIL/* selftest.py; fi - # write html report, then ignore. Coverage needs to be combined first - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi after_success: - - ls -l .coverage* - - coverage combine - coverage report - coveralls - pip install pep8 pyflakes From 3807af79e31af0d268b555e2370f7ab14c4c1761 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 22:18:40 +0300 Subject: [PATCH 273/488] Add a test for PalmImagePlugin.py --- Tests/test_file_palm.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Tests/test_file_palm.py diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py new file mode 100644 index 000000000..62c8396b2 --- /dev/null +++ b/Tests/test_file_palm.py @@ -0,0 +1,24 @@ +from helper import unittest, PillowTestCase, tearDownModule, lena + +import os.path + + +class TestFilePalm(PillowTestCase): + + def test_save_palm_p(self): + # Arrange + im = lena("P") + outfile = self.tempfile('temp_p.palm') + + # Act + im.save(outfile) + + # Assert + self.assertTrue(os.path.isfile(outfile)) + self.assertGreater(os.path.getsize(outfile), 0) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 775307113bf7c16490829af40467a3defab6443a Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 22:36:56 +0300 Subject: [PATCH 274/488] Ensure rowbytes is an integer (fix for Python 3) --- PIL/PalmImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/PalmImagePlugin.py b/PIL/PalmImagePlugin.py index 5f4c08490..bba1de8bb 100644 --- a/PIL/PalmImagePlugin.py +++ b/PIL/PalmImagePlugin.py @@ -180,7 +180,7 @@ def _save(im, fp, filename, check=0): cols = im.size[0] rows = im.size[1] - rowbytes = ((cols + (16//bpp - 1)) / (16 // bpp)) * 2 + rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2 transparent_index = 0 compression_type = _COMPRESSION_TYPES["none"] From 19bf3390a91052938915a36bf9cca9f9853f824c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 1 Jul 2014 12:53:15 -0700 Subject: [PATCH 275/488] Removed extra print --- Tests/test_file_jpeg2k.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index e115df3a8..23564c434 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -44,7 +44,6 @@ class TestFileJpeg2k(PillowTestCase): data = BytesIO(f.read()) im = Image.open(data) im.load() - print ("bytesio") self.assert_image_similar(im, test_card, 1.0e-3) # These two test pre-written JPEG 2000 files that were not written with From b6f836b5b8fdb5884172e87d15fa443bdd80a058 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 1 Jul 2014 22:53:30 +0300 Subject: [PATCH 276/488] Refactor and add two more tests --- Tests/test_file_palm.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 62c8396b2..c9e3c8c37 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -5,10 +5,10 @@ import os.path class TestFilePalm(PillowTestCase): - def test_save_palm_p(self): + def helper_save_as_palm(self, mode): # Arrange - im = lena("P") - outfile = self.tempfile('temp_p.palm') + im = lena(mode) + outfile = self.tempfile("temp_" + mode + ".palm") # Act im.save(outfile) @@ -17,6 +17,27 @@ class TestFilePalm(PillowTestCase): self.assertTrue(os.path.isfile(outfile)) self.assertGreater(os.path.getsize(outfile), 0) + def test_monochrome(self): + # Arrange + mode = "1" + + # Act / Assert + self.helper_save_as_palm(mode) + + def test_p_mode(self): + # Arrange + mode = "P" + + # Act / Assert + self.helper_save_as_palm(mode) + + def test_rgb_ioerror(self): + # Arrange + mode = "RGB" + + # Act / Assert + self.assertRaises(IOError, lambda: self.helper_save_as_palm(mode)) + if __name__ == '__main__': unittest.main() From 80d6137c860b9322572ee1390514df1975acb2e7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 1 Jul 2014 18:39:40 -0400 Subject: [PATCH 277/488] Bump [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index f19ae0683..b80639db7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Changelog (Pillow) ================== -2.5.0 (unreleased) +2.5.0 (2014-07-01) ------------------ - Imagedraw rewrite From 08a9bdbcd65059159fe4e518ce7f878557852980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20J=C3=B8rgen=20Solberg?= Date: Wed, 2 Jul 2014 21:27:52 +0200 Subject: [PATCH 278/488] Fix dispose calculations - use correct dispose mode - only apply the dispose on extent of the previous frame --- PIL/GifImagePlugin.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index ec8301973..c53a61c94 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -96,6 +96,7 @@ class GifImageFile(ImageFile.ImageFile): # rewind self.__offset = 0 self.dispose = None + self.dispose_extent = [0, 0, 0, 0] #x0, y0, x1, y1 self.__frame = -1 self.__fp.seek(self.__rewind) @@ -114,12 +115,12 @@ class GifImageFile(ImageFile.ImageFile): self.__offset = 0 if self.dispose: - self.im = self.dispose - self.dispose = None + self.im.paste(self.dispose, self.dispose_extent) from copy import copy self.palette = copy(self.global_palette) + disposal_method = 0 while True: s = self.fp.read(1) @@ -140,17 +141,10 @@ class GifImageFile(ImageFile.ImageFile): if flags & 1: self.info["transparency"] = i8(block[3]) self.info["duration"] = i16(block[1:3]) * 10 - try: - # disposal methods - if flags & 8: - # replace with background colour - self.dispose = Image.core.fill("P", self.size, - self.info["background"]) - elif flags & 16: - # replace with previous contents - self.dispose = self.im.copy() - except (AttributeError, KeyError): - pass + + # disposal method - find the value of bits 4 - 6 + disposal_method = 0b00011100 & flags + disposal_method = disposal_method >> 2 elif i8(s) == 255: # # application extension @@ -172,6 +166,7 @@ class GifImageFile(ImageFile.ImageFile): # extent x0, y0 = i16(s[0:]), i16(s[2:]) x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) + self.dispose_extent = x0, y0, x1, y1 flags = i8(s[8]) interlace = (flags & 64) != 0 @@ -194,6 +189,26 @@ class GifImageFile(ImageFile.ImageFile): pass # raise IOError, "illegal GIF tag `%x`" % i8(s) + try: + if disposal_method < 2: + # do not dispose or none specified + self.dispose = None + elif disposal_method == 2: + # replace with background colour + self.dispose = Image.core.fill("P", self.size, + self.info["background"]) + else: + # replace with previous contents + if self.im: + self.dispose = self.im.copy() + + # only dispose the extent in this frame + if self.dispose: + self.dispose = self.dispose.crop(self.dispose_extent) + except (AttributeError, KeyError): + pass + + if not self.tile: # self.__fp = None raise EOFError("no more images in GIF file") From bcee472fc5743ec65c55735c865de8729df5d8bd Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 3 Jul 2014 07:06:02 -0400 Subject: [PATCH 279/488] Image-sig is dead [ci skip] Image-sig is dead, long live GitHub & IRC --- docs/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index a8c204228..3593b2535 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,7 +44,6 @@ PIL needs you! Please help us maintain the Python Imaging Library here: - `GitHub `_ - `Freenode `_ -- `Image-SIG `_ Financial --------- From 8d91b8dab5f9ec36a45c75ad3440cee18bc35d92 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 3 Jul 2014 07:11:51 -0400 Subject: [PATCH 280/488] Image-sig is dead [ci skip] Image-sig is dead, long live GitHub & IRC --- docs/about.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/about.rst b/docs/about.rst index 817773610..919b2918c 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -9,12 +9,10 @@ The fork authors' goal is to foster active development of PIL through: - Continuous integration testing via `Travis CI`_ - Publicized development activity on `GitHub`_ - Regular releases to the `Python Package Index`_ -- Solicitation for community contributions and involvement on `Image-SIG`_ .. _Travis CI: https://travis-ci.org/python-pillow/Pillow .. _GitHub: https://github.com/python-pillow/Pillow .. _Python Package Index: https://pypi.python.org/pypi/Pillow -.. _Image-SIG: http://mail.python.org/mailman/listinfo/image-sig License ------- From f115b7d664732e61d564f26024d98dcd6ed6447f Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 3 Jul 2014 07:14:59 -0400 Subject: [PATCH 281/488] Image-sig is dead [ci skip] Image-sig is dead, long live GitHub & IRC. Also cleanup HTML. --- docs/_templates/sidebarhelp.html | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/_templates/sidebarhelp.html b/docs/_templates/sidebarhelp.html index ce36d2e34..bfc14bf66 100644 --- a/docs/_templates/sidebarhelp.html +++ b/docs/_templates/sidebarhelp.html @@ -1,18 +1,8 @@

Need help?

-

- You can seek realtime assistance via IRC at - irc://irc.freenode.net#pil. You can - also post to the - - Image-SIG mailing list. And, of course, there's - - Stack Overflow. + You can seek assistance via IRC at irc://irc.freenode.net#pil and report issues on GitHub.

- If you've discovered a bug, you can - open an issue - on Github. + And, of course, there's Stack Overflow.

- From 4ffa569baf1db5eb122ccf3e6770a839c22f7aa9 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 3 Jul 2014 07:33:26 -0400 Subject: [PATCH 282/488] Clean up Move old dists download instructions to installation instructions. --- docs/index.rst | 10 ++-------- docs/installation.rst | 5 +++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 3593b2535..d852bc4d3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,8 +1,7 @@ Pillow ====== -Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the -Python Imaging Library by Fredrik Lundh and Contributors. +Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master :target: https://travis-ci.org/python-pillow/Pillow @@ -20,12 +19,7 @@ Python Imaging Library by Fredrik Lundh and Contributors. :target: https://coveralls.io/r/python-pillow/Pillow?branch=master :alt: Test coverage -To start using Pillow, please read the :doc:`installation -instructions `. - -You can get the source and contribute at -https://github.com/python-pillow/Pillow. You can download archives -and old versions from `PyPI `_. +To install Pillow, please read the :doc:`installation instructions `. To download the source code and/or contribute to the development process please see: https://github.com/python-pillow/Pillow. .. toctree:: :maxdepth: 2 diff --git a/docs/installation.rst b/docs/installation.rst index 969a8ee8f..96d00469e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -253,3 +253,8 @@ current versions of Linux, OS X, and Windows. +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows 8.1 Pro |Yes | 2.6,2.7,3.2,3.3,3.4 | 2.3.0, 2.4.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ + +Old Versions +------------ + +You can download old distributions from `PyPI `_. Only the latest 1.x and 2.x releases are visible, but all releases are available by direct URL access e.g. https://pypi.python.org/pypi/Pillow/1.0. From a082f50121be9774c928a849ba1322bbb8767b09 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 3 Jul 2014 07:38:50 -0400 Subject: [PATCH 283/488] Clean up ; wording [ci skip] --- docs/index.rst | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index d852bc4d3..0725edad3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,7 @@ Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the Pyt :target: https://coveralls.io/r/python-pillow/Pillow?branch=master :alt: Test coverage -To install Pillow, please read the :doc:`installation instructions `. To download the source code and/or contribute to the development process please see: https://github.com/python-pillow/Pillow. +To install Pillow, please read the :doc:`installation instructions `. To download the source code and/or contribute to the development of Pillow please see: https://github.com/python-pillow/Pillow. .. toctree:: :maxdepth: 2 @@ -31,29 +31,6 @@ To install Pillow, please read the :doc:`installation instructions `_ -- `Freenode `_ - -Financial ---------- - -Pillow is a volunteer effort led by Alex Clark. If you can't help with -development please consider helping us financially. Your assistance would -be very much appreciated! - -.. note:: Contributors please add your name and donation preference here. - -======================================= ======================================= -**Developer** **Preference** -======================================= ======================================= -Alex Clark (fork author) http://gittip.com/aclark4life -======================================= ======================================= - Indices and tables ================== From b7954d8f025ee174c8e6b0506fbe0c9dccc3a85d Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 3 Jul 2014 15:09:33 +0300 Subject: [PATCH 284/488] No need to send empty PyPy coverage to Coveralls Otherwise it just shows up as grey, question mark: https://coveralls.io/builds/929022 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0ff62ccdd..e7a06956f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,8 @@ script: after_success: - coverage report - - coveralls + # No need to send empty coverage to Coveralls for PyPy + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coveralls; fi - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py From f88a355c6c3bc0bced52bc14ec3b4bad99c261e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20J=C3=B8rgen=20Solberg?= Date: Thu, 3 Jul 2014 18:48:12 +0200 Subject: [PATCH 285/488] Handle transparency between frames for animated GIFs Show the previous frame for transparent pixels when the disposal method is 'do not dispose'. This fixes issue 634. --- PIL/GifImagePlugin.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index c53a61c94..7eeaae646 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -99,6 +99,8 @@ class GifImageFile(ImageFile.ImageFile): self.dispose_extent = [0, 0, 0, 0] #x0, y0, x1, y1 self.__frame = -1 self.__fp.seek(self.__rewind) + self._prev_im = None + self.disposal_method = 0 if frame != self.__frame + 1: raise ValueError("cannot seek to frame %d" % frame) @@ -120,7 +122,6 @@ class GifImageFile(ImageFile.ImageFile): from copy import copy self.palette = copy(self.global_palette) - disposal_method = 0 while True: s = self.fp.read(1) @@ -143,8 +144,8 @@ class GifImageFile(ImageFile.ImageFile): self.info["duration"] = i16(block[1:3]) * 10 # disposal method - find the value of bits 4 - 6 - disposal_method = 0b00011100 & flags - disposal_method = disposal_method >> 2 + self.disposal_method = 0b00011100 & flags + self.disposal_method = self.disposal_method >> 2 elif i8(s) == 255: # # application extension @@ -190,10 +191,10 @@ class GifImageFile(ImageFile.ImageFile): # raise IOError, "illegal GIF tag `%x`" % i8(s) try: - if disposal_method < 2: + if self.disposal_method < 2: # do not dispose or none specified self.dispose = None - elif disposal_method == 2: + elif self.disposal_method == 2: # replace with background colour self.dispose = Image.core.fill("P", self.size, self.info["background"]) @@ -220,6 +221,18 @@ class GifImageFile(ImageFile.ImageFile): def tell(self): return self.__frame + def load_end(self): + ImageFile.ImageFile.load_end(self) + + # if the disposal method is 'do not dispose', transparent + # pixels should show the content of the previous frame + if self._prev_im and self.disposal_method == 1: + # we do this by pasting the updated area onto the previous + # frame which we then use as the current image content + updated = self.im.crop(self.dispose_extent) + self._prev_im.paste(updated, self.dispose_extent, updated.convert('RGBA')) + self.im = self._prev_im + self._prev_im = self.im.copy() # -------------------------------------------------------------------- # Write GIF files From 047832c6c667c473cd5013e2fba1ab3f5bd291bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20J=C3=B8rgen=20Solberg?= Date: Thu, 3 Jul 2014 19:01:18 +0200 Subject: [PATCH 286/488] only update the disposal_method if it not 'unspecified' --- PIL/GifImagePlugin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 7eeaae646..cb96b9c95 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -144,8 +144,14 @@ class GifImageFile(ImageFile.ImageFile): self.info["duration"] = i16(block[1:3]) * 10 # disposal method - find the value of bits 4 - 6 - self.disposal_method = 0b00011100 & flags - self.disposal_method = self.disposal_method >> 2 + dispose_bits = 0b00011100 & flags + dispose_bits = dispose_bits >> 2 + if dispose_bits: + # only set the dispose if it is not + # unspecified. I'm not sure if this is + # correct, but it seems to prevent the last + # frame from looking odd for some animations + self.disposal_method = dispose_bits elif i8(s) == 255: # # application extension From c745b9abedfc8402ce16cd8999eb59f12be6868d Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 4 Jul 2014 16:53:26 +0300 Subject: [PATCH 287/488] Change hyperlinks to rst format [CI skip] --- docs/reference/Image.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 6dcb73638..f212ba7a6 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -49,7 +49,8 @@ Functions .. autofunction:: open - .. warning:: > To protect against potential DOS attacks caused by "[decompression bombs](https://en.wikipedia.org/wiki/Zip_bomb)" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also [the logging documentation](https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module) to have warnings output to the logging facility instead of stderr. + .. warning:: > To protect against potential DOS attacks caused by " +`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr. Image processing ^^^^^^^^^^^^^^^^ From 44c4eaebc861342c7b89230f8f04079b7c5eb759 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 4 Jul 2014 17:01:52 +0300 Subject: [PATCH 288/488] Change hyperlinks to rst format [CI skip] --- docs/reference/Image.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index f212ba7a6..bf64c835d 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -49,8 +49,7 @@ Functions .. autofunction:: open - .. warning:: > To protect against potential DOS attacks caused by " -`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr. + .. warning:: > To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr. Image processing ^^^^^^^^^^^^^^^^ From e34724b8e43e9e841f95c752a95c2fc7339fc25e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 4 Jul 2014 15:00:34 -0400 Subject: [PATCH 289/488] Wording [ci skip] --- docs/_templates/sidebarhelp.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/_templates/sidebarhelp.html b/docs/_templates/sidebarhelp.html index bfc14bf66..e07180a99 100644 --- a/docs/_templates/sidebarhelp.html +++ b/docs/_templates/sidebarhelp.html @@ -1,8 +1,4 @@

Need help?

- You can seek assistance via IRC at irc://irc.freenode.net#pil and report issues on GitHub. -

- -

- And, of course, there's Stack Overflow. + You can get help via IRC at irc://irc.freenode.net#pil or Stack Overflow here and here. Please report issues on GitHub.

From 167f96db8d97013bfa98e408c709fb4d5b1759aa Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 4 Jul 2014 15:12:31 -0400 Subject: [PATCH 290/488] Wording [ci skip] --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 0725edad3..16e450856 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,7 @@ Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the Pyt :target: https://coveralls.io/r/python-pillow/Pillow?branch=master :alt: Test coverage -To install Pillow, please read the :doc:`installation instructions `. To download the source code and/or contribute to the development of Pillow please see: https://github.com/python-pillow/Pillow. +To install Pillow, please follow the :doc:`installation instructions `. To download source and/or contribute to development of Pillow please see: https://github.com/python-pillow/Pillow. .. toctree:: :maxdepth: 2 From aa4ff9265d82932108715ef6ef98004f84eb500e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 4 Jul 2014 15:17:23 -0400 Subject: [PATCH 291/488] Factor and move note --- docs/installation.rst | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 96d00469e..8baca2f8d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -44,6 +44,10 @@ run:: External libraries ------------------ +.. note:: + + You *do not* need to install all of the external libraries to use Pillow's basic features. + Many of Pillow's features require external libraries: * **libjpeg** provides JPEG functionality. @@ -92,11 +96,6 @@ Linux installation Fedora, Debian/Ubuntu, and ArchLinux include Pillow (instead of PIL) with their distributions. Consider using those instead of installing manually. -.. note:: - - You *do not* need to install all of the external libraries to get Pillow's - basics to work. - **We do not provide binaries for Linux.** If you didn't build Python from source, make sure you have Python's development libraries installed. In Debian or Ubuntu:: @@ -131,11 +130,6 @@ Prerequisites are installed on **Fedora 20** with:: Mac OS X installation --------------------- -.. note:: - - You *do not* need to install all of the external libraries to get Pillow's - basics to work. - **We do not provide binaries for OS X**, so you'll need XCode to install Pillow. (XCode 4.2 on 10.6 will work with the Official Python binary distribution. Otherwise, use whatever XCode you used to compile Python.) From e4c0a306f9c642c67e78ef7ef05b3987e5de307b Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 4 Jul 2014 15:22:30 -0400 Subject: [PATCH 292/488] We now provide OS X binaries ; clean up --- docs/installation.rst | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8baca2f8d..f99030219 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -130,17 +130,13 @@ Prerequisites are installed on **Fedora 20** with:: Mac OS X installation --------------------- -**We do not provide binaries for OS X**, so you'll need XCode to install -Pillow. (XCode 4.2 on 10.6 will work with the Official Python binary -distribution. Otherwise, use whatever XCode you used to compile Python.) +We provide binaries for Windows in the form of `Python Wheels `_. Alternatively you can compile Pillow with with XCode. -The easiest way to install the prerequisites is via `Homebrew -`_. After you install Homebrew, run:: +The easiest way to install external libraries is via `Homebrew `_. After you install Homebrew, run:: $ brew install libtiff libjpeg webp little-cms2 -If you've built your own Python, then you should be able to install Pillow -using:: +Install Pillow with:: $ pip install Pillow From 65593a3827dfef5f5c8e92bfbb1a8ff0c2c64af4 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 00:04:19 +0300 Subject: [PATCH 293/488] More tests for ImageFont.py --- .coveragerc | 4 ++- Tests/test_imagefont.py | 74 +++++++++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/.coveragerc b/.coveragerc index 87e3e968f..39ae20ac6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -11,4 +11,6 @@ exclude_lines = if __name__ == .__main__.: # Don't complain about debug code if Image.DEBUG: - if DEBUG: \ No newline at end of file + if DEBUG: + # Don't complain about Windows code as Travis is Linux + if sys.platform == "win32": diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 927c80bee..ea1b13cc6 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -5,8 +5,8 @@ from PIL import ImageDraw from io import BytesIO import os -font_path = "Tests/fonts/FreeMono.ttf" -font_size = 20 +FONT_PATH = "Tests/fonts/FreeMono.ttf" +FONT_SIZE = 20 try: @@ -20,17 +20,17 @@ try: ImageFont.core.freetype2_version, "\d+\.\d+\.\d+$") def test_font_with_name(self): - ImageFont.truetype(font_path, font_size) - self._render(font_path) + ImageFont.truetype(FONT_PATH, FONT_SIZE) + self._render(FONT_PATH) self._clean() def _font_as_bytes(self): - with open(font_path, 'rb') as f: + with open(FONT_PATH, 'rb') as f: font_bytes = BytesIO(f.read()) return font_bytes def test_font_with_filelike(self): - ImageFont.truetype(self._font_as_bytes(), font_size) + ImageFont.truetype(self._font_as_bytes(), FONT_SIZE) self._render(self._font_as_bytes()) # Usage note: making two fonts from the same buffer fails. # shared_bytes = self._font_as_bytes() @@ -39,18 +39,18 @@ try: self._clean() def test_font_with_open_file(self): - with open(font_path, 'rb') as f: + with open(FONT_PATH, 'rb') as f: self._render(f) self._clean() def test_font_old_parameters(self): self.assert_warning( DeprecationWarning, - lambda: ImageFont.truetype(filename=font_path, size=font_size)) + lambda: ImageFont.truetype(filename=FONT_PATH, size=FONT_SIZE)) def _render(self, font): txt = "Hello World!" - ttf = ImageFont.truetype(font, font_size) + ttf = ImageFont.truetype(font, FONT_SIZE) w, h = ttf.getsize(txt) img = Image.new("RGB", (256, 64), "white") d = ImageDraw.Draw(img) @@ -63,8 +63,8 @@ try: os.unlink('font.png') def test_render_equal(self): - img_path = self._render(font_path) - with open(font_path, 'rb') as f: + img_path = self._render(FONT_PATH) + with open(FONT_PATH, 'rb') as f: font_filelike = BytesIO(f.read()) img_filelike = self._render(font_filelike) @@ -74,7 +74,7 @@ try: def test_render_multiline(self): im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) - ttf = ImageFont.truetype(font_path, font_size) + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) line_spacing = draw.textsize('A', font=ttf)[1] + 8 lines = ['hey you', 'you are awesome', 'this looks awkward'] y = 0 @@ -94,7 +94,7 @@ try: img_grey = Image.new("L", (100, 100)) draw = ImageDraw.Draw(img_grey) word = "testing" - font = ImageFont.truetype(font_path, font_size) + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) orientation = Image.ROTATE_90 transposed_font = ImageFont.TransposedFont( @@ -116,7 +116,7 @@ try: img_grey = Image.new("L", (100, 100)) draw = ImageDraw.Draw(img_grey) word = "testing" - font = ImageFont.truetype(font_path, font_size) + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) orientation = None transposed_font = ImageFont.TransposedFont( @@ -133,6 +133,52 @@ try: # Check boxes a and b are same size self.assertEqual(box_size_a, box_size_b) + def test_free_type_font_get_name(self): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + # Act + name = font.getname() + + # Assert + self.assertEqual(('FreeMono', 'Regular'), name) + + def test_free_type_font_get_metrics(self): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + # Act + ascent, descent = font.getmetrics() + + # Assert + self.assertIsInstance(ascent, int) + self.assertIsInstance(descent, int) + self.assertEqual((ascent, descent), (16, 4)) # too exact check? + + def test_load_path_not_found(self): + # Arrange + filename = "somefilenamethatdoesntexist.ttf" + + # Act/Assert + self.assertRaises(IOError, lambda: ImageFont.load_path(filename)) + + def test_default_font(self): + # Arrange + txt = 'This is a "better than nothing" default font.' + im = Image.new(mode='RGB', size=(300, 100)) + draw = ImageDraw.Draw(im) + + target = 'Tests/images/default_font.png' + target_img = Image.open(target) + + # Act + default_font = ImageFont.load_default() + draw.text((10, 10), txt, font=default_font) + + # Assert + self.assert_image_equal(im, target_img) + + except ImportError: class TestImageFont(PillowTestCase): def test_skip(self): From ff6a0b9b8c9f97954814fdd0adf2a9c32171c7c6 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 00:18:52 +0300 Subject: [PATCH 294/488] Add test image using ImageFont's default font --- Tests/images/default_font.png | Bin 0 -> 586 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/default_font.png diff --git a/Tests/images/default_font.png b/Tests/images/default_font.png new file mode 100644 index 0000000000000000000000000000000000000000..bb4862b996fe733cc94fb396ecc9209e9fbbedcf GIT binary patch literal 586 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!Q#hD_uc6 zKy~{6|I;r!U*_p%;Lb6>`?7j@8^fdy!_?bAEl}`7_uI;Q{l_0$=B#J0s}sF#EPSpn zieqA@(Bz$3dyD4P*oAw#?SH@h#1xy|I_v*Eosh!GZGUhr|Eu|jZ!C2b`n^wo$qutu z7oJb5JF8Baa%Y4T=)*Ln5Fn_pFPc!H-sEpCwEY5?WR?6$Fdh!_50cVukZ-d{@o=PUA6ewe&0FI4d(wi zzF^MHT}fZ=g`b@%UY2mva&ls^Dc@Hu+3zRU)TBK#Rx?YC`}yun{?s!oo_t$f7I^-R znOu*T+ndPhwR`RsU488J|MJy{veRmJ{?(@YygmGJMfQLB8~cxFgnrqy)Ld)vc9HwV zwSO)9zgF;`Uw`b%?f-9${pW~YWxTsbdt0*1UC#48vuxkZnr&nDEo#>Fmb=QJ0!x@1e#g8Q+<_y)dj`?{B`D@u%(7Qr}9jz~YAz b`oT-)*Xv};1n&#bP0l+XkKI4k`b literal 0 HcmV?d00001 From 770ef9312be9c6e10e9d3b31d27c12b4f8d7cf1a Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 01:02:46 +0300 Subject: [PATCH 295/488] flake8 and fix path in __main__ --- PIL/ImageFont.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py index 18d09b871..25993007d 100644 --- a/PIL/ImageFont.py +++ b/PIL/ImageFont.py @@ -29,13 +29,15 @@ from __future__ import print_function from PIL import Image from PIL._util import isDirectory, isPath -import os, sys +import os +import sys try: import warnings except ImportError: warnings = None + class _imagingft_not_installed: # module placeholder def __getattr__(self, id): @@ -90,8 +92,8 @@ class ImageFont: # read PILfont header if file.readline() != b"PILfont\n": raise SyntaxError("Not a PILfont file") - d = file.readline().split(b";") - self.info = [] # FIXME: should be a dictionary + file.readline().split(b";") + self.info = [] # FIXME: should be a dictionary while True: s = file.readline() if not s or s == b"DATA\n": @@ -113,6 +115,7 @@ class ImageFont: self.getsize = self.font.getsize self.getmask = self.font.getmask + ## # Wrapper for FreeType fonts. Application code should use the # truetype factory function to create font objects. @@ -124,14 +127,18 @@ class FreeTypeFont: # FIXME: use service provider instead if file: if warnings: - warnings.warn('file parameter deprecated, please use font parameter instead.', DeprecationWarning) + warnings.warn( + 'file parameter deprecated, ' + 'please use font parameter instead.', + DeprecationWarning) font = file if isPath(font): self.font = core.getfont(font, size, index, encoding) else: self.font_bytes = font.read() - self.font = core.getfont("", size, index, encoding, self.font_bytes) + self.font = core.getfont( + "", size, index, encoding, self.font_bytes) def getname(self): return self.font.family, self.font.style @@ -151,7 +158,7 @@ class FreeTypeFont: def getmask2(self, text, mode="", fill=Image.core.fill): size, offset = self.font.getsize(text) im = fill("L", size, 0) - self.font.render(text, im.id, mode=="1") + self.font.render(text, im.id, mode == "1") return im, offset ## @@ -163,12 +170,13 @@ class FreeTypeFont: # be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM, # Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270. + class TransposedFont: "Wrapper for writing rotated or mirrored text" def __init__(self, font, orientation=None): self.font = font - self.orientation = orientation # any 'transpose' argument, or None + self.orientation = orientation # any 'transpose' argument, or None def getsize(self, text): w, h = self.font.getsize(text) @@ -221,7 +229,10 @@ def truetype(font=None, size=10, index=0, encoding="", filename=None): if filename: if warnings: - warnings.warn('filename parameter deprecated, please use font parameter instead.', DeprecationWarning) + warnings.warn( + 'filename parameter deprecated, ' + 'please use font parameter instead.', + DeprecationWarning) font = filename try: @@ -272,8 +283,8 @@ def load_default(): import base64 f = ImageFont() f._load_pilfont_data( - # courB08 - BytesIO(base64.decodestring(b''' + # courB08 + BytesIO(base64.decodestring(b''' UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @@ -395,8 +406,8 @@ w7IkEbzhVQAAAABJRU5ErkJggg== if __name__ == "__main__": # create font data chunk for embedding - import base64, os, sys - font = "../Tests/images/courB08" + import base64 + font = "Tests/images/courB08" print(" f._load_pilfont_data(") print(" # %s" % os.path.basename(font)) print(" BytesIO(base64.decodestring(b'''") From 8e6ef35f78402279cabf4c4a3372651379d8c138 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 4 Jul 2014 18:54:14 -0400 Subject: [PATCH 296/488] Fix error(s) --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index f99030219..8abbd6679 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -130,7 +130,7 @@ Prerequisites are installed on **Fedora 20** with:: Mac OS X installation --------------------- -We provide binaries for Windows in the form of `Python Wheels `_. Alternatively you can compile Pillow with with XCode. +We provide binaries for OS X in the form of `Python Wheels `_. Alternatively you can compile Pillow with with XCode. The easiest way to install external libraries is via `Homebrew `_. After you install Homebrew, run:: From 8a4081c5bc0c6fc12756109b87b6ac0f2b5ac5c9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 12:08:35 +0300 Subject: [PATCH 297/488] More tests for ImageMath --- Tests/test_imagemath.py | 84 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 35d75dbbd..a03b25cce 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -14,9 +14,13 @@ def pixel(im): A = Image.new("L", (1, 1), 1) B = Image.new("L", (1, 1), 2) +Z = Image.new("L", (1, 1), 0) # Z for zero F = Image.new("F", (1, 1), 3) I = Image.new("I", (1, 1), 4) +A2 = A.resize((2, 2)) +B2 = B.resize((2, 2)) + images = {"A": A, "B": B, "F": F, "I": I} @@ -71,6 +75,86 @@ class TestImageMath(PillowTestCase): self.assertEqual(pixel(ImageMath.eval("A == 1", images)), "I 1") self.assertEqual(pixel(ImageMath.eval("A == 2", images)), "I 0") + def test_one_image_larger(self): + self.assertEqual(pixel(ImageMath.eval("A+B", A=A2, B=B)), "I 3") + self.assertEqual(pixel(ImageMath.eval("A+B", A=A, B=B2)), "I 3") + + def test_abs(self): + self.assertEqual(pixel(ImageMath.eval("abs(A)", A=A)), "I 1") + self.assertEqual(pixel(ImageMath.eval("abs(B)", B=B)), "I 2") + + def test_bitwise_invert(self): + self.assertEqual(pixel(ImageMath.eval("~Z", Z=Z)), "I -1") + self.assertEqual(pixel(ImageMath.eval("~A", A=A)), "I -2") + self.assertEqual(pixel(ImageMath.eval("~B", B=B)), "I -3") + + def test_bitwise_and(self): + self.assertEqual(pixel(ImageMath.eval("Z&Z", A=A, Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z&A", A=A, Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A&Z", A=A, Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A&A", A=A, Z=Z)), "I 1") + + def test_bitwise_or(self): + self.assertEqual(pixel(ImageMath.eval("Z|Z", A=A, Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z|A", A=A, Z=Z)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A|Z", A=A, Z=Z)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A|A", A=A, Z=Z)), "I 1") + + def test_bitwise_xor(self): + self.assertEqual(pixel(ImageMath.eval("Z^Z", A=A, Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z^A", A=A, Z=Z)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A^Z", A=A, Z=Z)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A^A", A=A, Z=Z)), "I 0") + + def test_bitwise_leftshift(self): + self.assertEqual(pixel(ImageMath.eval("Z<<0", Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z<<1", Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A<<0", A=A)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A<<1", A=A)), "I 2") + + def test_bitwise_rightshift(self): + self.assertEqual(pixel(ImageMath.eval("Z>>0", Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z>>1", Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A>>0", A=A)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A>>1", A=A)), "I 0") + + def test_logical_eq(self): + self.assertEqual(pixel(ImageMath.eval("A==A", A=A)), "I 1") + self.assertEqual(pixel(ImageMath.eval("B==B", B=B)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A==B", A=A, B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B==A", A=A, B=B)), "I 0") + + def test_logical_ne(self): + self.assertEqual(pixel(ImageMath.eval("A!=A", A=A)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B!=B", B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A!=B", A=A, B=B)), "I 1") + self.assertEqual(pixel(ImageMath.eval("B!=A", A=A, B=B)), "I 1") + + def test_logical_lt(self): + self.assertEqual(pixel(ImageMath.eval("AA", A=A)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B>B", B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A>B", A=A, B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B>A", A=A, B=B)), "I 1") + + def test_logical_ge(self): + self.assertEqual(pixel(ImageMath.eval("A>=A", A=A)), "I 1") + self.assertEqual(pixel(ImageMath.eval("B>=B", B=B)), "I 1") + self.assertEqual(pixel(ImageMath.eval("A>=B", A=A, B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B>=A", A=A, B=B)), "I 1") + + if __name__ == '__main__': unittest.main() From 5f2138d91588f792624ca5ebf599698be88f8879 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 12:13:43 +0300 Subject: [PATCH 298/488] flake8 ImageMath.py --- PIL/ImageMath.py | 53 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/PIL/ImageMath.py b/PIL/ImageMath.py index adfcc4f6f..4dcc5125c 100644 --- a/PIL/ImageMath.py +++ b/PIL/ImageMath.py @@ -26,9 +26,11 @@ except ImportError: VERBOSE = 0 + def _isconstant(v): return isinstance(v, int) or isinstance(v, float) + class _Operand: # wraps an image operand, providing standard operators @@ -68,20 +70,25 @@ class _Operand: im2 = self.__fixup(im2) if im1.mode != im2.mode: # convert both arguments to floating point - if im1.mode != "F": im1 = im1.convert("F") - if im2.mode != "F": im2 = im2.convert("F") + if im1.mode != "F": + im1 = im1.convert("F") + if im2.mode != "F": + im2 = im2.convert("F") if im1.mode != im2.mode: raise ValueError("mode mismatch") if im1.size != im2.size: # crop both arguments to a common size size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1])) - if im1.size != size: im1 = im1.crop((0, 0) + size) - if im2.size != size: im2 = im2.crop((0, 0) + size) + if im1.size != size: + im1 = im1.crop((0, 0) + size) + if im2.size != size: + im2 = im2.crop((0, 0) + size) out = Image.new(mode or im1.mode, size, None) else: out = Image.new(mode or im1.mode, im1.size, None) - im1.load(); im2.load() + im1.load() + im2.load() try: op = getattr(_imagingmath, op+"_"+im1.mode) except AttributeError: @@ -101,34 +108,47 @@ class _Operand: def __abs__(self): return self.apply("abs", self) + def __pos__(self): return self + def __neg__(self): return self.apply("neg", self) # binary operators def __add__(self, other): return self.apply("add", self, other) + def __radd__(self, other): return self.apply("add", other, self) + def __sub__(self, other): return self.apply("sub", self, other) + def __rsub__(self, other): return self.apply("sub", other, self) + def __mul__(self, other): return self.apply("mul", self, other) + def __rmul__(self, other): return self.apply("mul", other, self) + def __truediv__(self, other): return self.apply("div", self, other) + def __rtruediv__(self, other): return self.apply("div", other, self) + def __mod__(self, other): return self.apply("mod", self, other) + def __rmod__(self, other): return self.apply("mod", other, self) + def __pow__(self, other): return self.apply("pow", self, other) + def __rpow__(self, other): return self.apply("pow", other, self) @@ -142,54 +162,77 @@ class _Operand: # bitwise def __invert__(self): return self.apply("invert", self) + def __and__(self, other): return self.apply("and", self, other) + def __rand__(self, other): return self.apply("and", other, self) + def __or__(self, other): return self.apply("or", self, other) + def __ror__(self, other): return self.apply("or", other, self) + def __xor__(self, other): return self.apply("xor", self, other) + def __rxor__(self, other): return self.apply("xor", other, self) + def __lshift__(self, other): return self.apply("lshift", self, other) + def __rshift__(self, other): return self.apply("rshift", self, other) # logical def __eq__(self, other): return self.apply("eq", self, other) + def __ne__(self, other): return self.apply("ne", self, other) + def __lt__(self, other): return self.apply("lt", self, other) + def __le__(self, other): return self.apply("le", self, other) + def __gt__(self, other): return self.apply("gt", self, other) + def __ge__(self, other): return self.apply("ge", self, other) + # conversions def imagemath_int(self): return _Operand(self.im.convert("I")) + + def imagemath_float(self): return _Operand(self.im.convert("F")) + # logical def imagemath_equal(self, other): return self.apply("eq", self, other, mode="I") + + def imagemath_notequal(self, other): return self.apply("ne", self, other, mode="I") + def imagemath_min(self, other): return self.apply("min", self, other) + + def imagemath_max(self, other): return self.apply("max", self, other) + def imagemath_convert(self, mode): return _Operand(self.im.convert(mode)) From 1aee9bfdef1d73007d8e35ab883c8c936c1da602 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 5 Jul 2014 23:16:14 +1000 Subject: [PATCH 299/488] Added class checking to __eq__ function --- PIL/Image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index 787e60270..ea8cc6155 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -573,6 +573,8 @@ class Image: return file def __eq__(self, other): + if self.__class__.__name__ != other.__class__.__name__: + return False a = (self.mode == other.mode) b = (self.size == other.size) c = (self.getpalette() == other.getpalette()) From f6f80e3a774f569010f56a061500c5c3b9a21e2f Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 16:29:40 +0300 Subject: [PATCH 300/488] Test case for #774 --- Tests/test_image.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index ec82eb419..e41447e42 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -44,6 +44,17 @@ class TestImage(PillowTestCase): file = self.tempfile("temp.ppm") im._dump(file) + def test_comparison_with_other_type(self): + # Arrange + item = Image.new('RGB', (25, 25), '#000') + num = 12 + + # Act/Assert + # Shouldn't cause AttributeError (#774) + self.assertFalse(item is None) + self.assertFalse(item == None) + self.assertFalse(item == num) + if __name__ == '__main__': unittest.main() From cedd8c2106f8dcacda33286a53e025b27f80b830 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 10:25:16 -0700 Subject: [PATCH 301/488] Failing test for messed up P image on write only format --- .travis.yml | 2 +- Tests/helper.py | 14 ++++++++++++++ Tests/test_file_palm.py | 21 ++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 57c2cb94b..738b94b71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ python: - 3.4 install: - - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake imagemagick" - "pip install cffi" - "pip install coveralls nose pyroma nose-cov" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi diff --git a/Tests/helper.py b/Tests/helper.py index 1c9851e25..4a4cb3a28 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -131,6 +131,17 @@ class PillowTestCase(unittest.TestCase): self.addCleanup(self.delete_tempfile, path) return path + def open_withImagemagick(self, f): + if not imagemagick_available(): + raise IOError() + + outfile = self.tempfile("temp.png") + if command_succeeds(['convert', f, outfile]): + from PIL import Image + return Image.open(outfile) + raise IOError() + + # helpers import sys @@ -194,4 +205,7 @@ def netpbm_available(): return command_succeeds(["ppmquant", "--help"]) and \ command_succeeds(["ppmtogif", "--help"]) +def imagemagick_available(): + return command_succeeds(['convert', '-version']) + # End of file diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index c9e3c8c37..95d89acb3 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -1,10 +1,11 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, tearDownModule, lena, imagemagick_available import os.path class TestFilePalm(PillowTestCase): - + _roundtrip = imagemagick_available() + def helper_save_as_palm(self, mode): # Arrange im = lena(mode) @@ -17,12 +18,25 @@ class TestFilePalm(PillowTestCase): self.assertTrue(os.path.isfile(outfile)) self.assertGreater(os.path.getsize(outfile), 0) + def roundtrip(self, mode): + if not self._roundtrip: + return + + im = lena(mode) + outfile = self.tempfile("temp.palm") + + im.save(outfile) + converted = self.open_withImagemagick(outfile) + self.assert_image_equal(converted, im) + + def test_monochrome(self): # Arrange mode = "1" # Act / Assert self.helper_save_as_palm(mode) + self.roundtrip(mode) def test_p_mode(self): # Arrange @@ -30,7 +44,8 @@ class TestFilePalm(PillowTestCase): # Act / Assert self.helper_save_as_palm(mode) - + self.roundtrip(mode) + def test_rgb_ioerror(self): # Arrange mode = "RGB" From 08d491f0065b3eaba7d2bfe26024300d69fda81b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 10:56:40 -0700 Subject: [PATCH 302/488] Skip known bad tests --- Tests/helper.py | 15 +++++++++++++++ Tests/test_file_palm.py | 1 + 2 files changed, 16 insertions(+) diff --git a/Tests/helper.py b/Tests/helper.py index 4a4cb3a28..7f0aaa73c 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -123,6 +123,21 @@ class PillowTestCase(unittest.TestCase): self.assertTrue(found) return result + def skipKnownBadTest(self, msg=None, platform=None, travis=None): + # Skip if platform/travis matches, and + # PILLOW_RUN_KNOWN_BAD is not true in the environment. + if bool(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)): + print (os.environ.get('PILLOW_RUN_KNOWN_BAD', False)) + return + + skip = True + if platform is not None: + skip = sys.platform.startswith(platform) + if travis is not None: + skip = skip and (travis == bool(os.environ.get('TRAVIS',False))) + if skip: + self.skipTest(msg or "Known Bad Test") + def tempfile(self, template): assert template[:5] in ("temp.", "temp_") (fd, path) = tempfile.mkstemp(template[4:], template[:4]) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 95d89acb3..c1947ff37 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -44,6 +44,7 @@ class TestFilePalm(PillowTestCase): # Act / Assert self.helper_save_as_palm(mode) + self.skipKnownBadTest("Palm P image is wrong") self.roundtrip(mode) def test_rgb_ioerror(self): From 318b405889ae4911a69ff45b3fa07cc246c16607 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 21:32:09 +0300 Subject: [PATCH 303/488] Don't exclude Windows code --- .coveragerc | 2 -- 1 file changed, 2 deletions(-) diff --git a/.coveragerc b/.coveragerc index 39ae20ac6..ea79190ae 100644 --- a/.coveragerc +++ b/.coveragerc @@ -12,5 +12,3 @@ exclude_lines = # Don't complain about debug code if Image.DEBUG: if DEBUG: - # Don't complain about Windows code as Travis is Linux - if sys.platform == "win32": From 4b4ed2c9b13d4144d9e877248dcb421b027b3b63 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 5 Jul 2014 21:47:20 +0300 Subject: [PATCH 304/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b80639db7..0bf1a7ee5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,13 @@ Changelog (Pillow) ================== +2.6.0 (unreleased) +------------------ + +- Test PalmImagePlugin and method to skip known bad tests + [hugovk, wiredfool] + + 2.5.0 (2014-07-01) ------------------ From d306d01c5edfa6fce6ce372ae207917d063238d6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 11:50:37 -0700 Subject: [PATCH 305/488] Document environment variable method for pip, #720 [ci skip] --- docs/installation.rst | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8abbd6679..a61213e15 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -79,15 +79,21 @@ Many of Pillow's features require external libraries: * Pillow has been tested with openjpeg **2.0.0** and **2.1.0**. -If the prerequisites are installed in the standard library locations for your -machine (e.g. :file:`/usr` or :file:`/usr/local`), no additional configuration -should be required. If they are installed in a non-standard location, you may -need to configure setuptools to use those locations (i.e. by editing -:file:`setup.py` and/or :file:`setup.cfg`). Once you have installed the -prerequisites, run:: +Once you have installed the prerequisites,run:: $ pip install Pillow +If the prerequisites are installed in the standard library locations +for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no +additional configuration should be required. If they are installed in +a non-standard location, you may need to configure setuptools to use +those locations by editing :file:`setup.py` or +:file:`setup.cfg`, or by adding environment variables on the command +line:: + + $ CFLAGS="-I/usr/pkg/include" pip install pillow + + Linux installation ------------------ From 01c0cc941757716dfb4162817cfa8a37362c62a1 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 5 Jul 2014 23:02:34 +0300 Subject: [PATCH 306/488] Remove unused WIP save2() from pre-fork days --- PIL/FontFile.py | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/PIL/FontFile.py b/PIL/FontFile.py index 7c5704c9d..26ddff0ac 100644 --- a/PIL/FontFile.py +++ b/PIL/FontFile.py @@ -17,8 +17,6 @@ import os from PIL import Image, _binary -import marshal - try: import zlib except ImportError: @@ -26,6 +24,7 @@ except ImportError: WIDTH = 800 + def puti16(fp, values): # write network order (big-endian) 16-bit sequence for v in values: @@ -33,6 +32,7 @@ def puti16(fp, values): v += 65536 fp.write(_binary.o16be(v)) + ## # Base class for raster font file handlers. @@ -95,9 +95,8 @@ class FontFile: # print chr(i), dst, s self.metrics[i] = d, dst, s - - def save1(self, filename): - "Save font in version 1 format" + def save(self, filename): + "Save font" self.compile() @@ -107,7 +106,7 @@ class FontFile: # font metrics fp = open(os.path.splitext(filename)[0] + ".pil", "wb") fp.write(b"PILfont\n") - fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!! + fp.write((";;;;;;%d;\n" % self.ysize).encode('ascii')) # HACK!!! fp.write(b"DATA\n") for id in range(256): m = self.metrics[id] @@ -117,30 +116,4 @@ class FontFile: puti16(fp, m[0] + m[1] + m[2]) fp.close() - - def save2(self, filename): - "Save font in version 2 format" - - # THIS IS WORK IN PROGRESS - - self.compile() - - data = marshal.dumps((self.metrics, self.info)) - - if zlib: - data = b"z" + zlib.compress(data, 9) - else: - data = b"u" + data - - fp = open(os.path.splitext(filename)[0] + ".pil", "wb") - - fp.write(b"PILfont2\n" + self.name + "\n" + "DATA\n") - - fp.write(data) - - self.bitmap.save(fp, "PNG") - - fp.close() - - - save = save1 # for now +# End of file From b7f56409eba64468d0d3feca3ac401d1413bdc12 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 14:30:34 -0700 Subject: [PATCH 307/488] Multiplication needs to be 64bit, to handle overflow regardless of the bittedness of the machine, fixes #771# --- libImaging/Storage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libImaging/Storage.c b/libImaging/Storage.c index d31db5cb2..c6d2e5c5e 100644 --- a/libImaging/Storage.c +++ b/libImaging/Storage.c @@ -379,7 +379,7 @@ ImagingNew(const char* mode, int xsize, int ysize) } else bytes = strlen(mode); /* close enough */ - if ((Py_ssize_t) xsize * ysize * bytes <= THRESHOLD) { + if ((int64_t) xsize * (int64_t) ysize * bytes <= THRESHOLD) { im = ImagingNewBlock(mode, xsize, ysize); if (im) return im; From 1008d6a993655d702ce0555f91c1a77ae10baa01 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 15:06:17 -0700 Subject: [PATCH 308/488] Windows compatibility --- libImaging/ImPlatform.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libImaging/ImPlatform.h b/libImaging/ImPlatform.h index be1f20f3f..70ee63119 100644 --- a/libImaging/ImPlatform.h +++ b/libImaging/ImPlatform.h @@ -69,4 +69,6 @@ #define FLOAT32 float #define FLOAT64 double - +#ifdef _MSC_VER +typedef signed __int64 int64_t; +#endif From f5931e82315a622eead063e1eb3eb26572ebe322 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 15:08:11 -0700 Subject: [PATCH 309/488] Test for #771, hangs in nose --- Tests/32bit_segfault_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Tests/32bit_segfault_test.py diff --git a/Tests/32bit_segfault_test.py b/Tests/32bit_segfault_test.py new file mode 100644 index 000000000..06fba80e1 --- /dev/null +++ b/Tests/32bit_segfault_test.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +import helper + +from PIL import Image + + +im = Image.new('L', (999999, 999999), 0) + + From e5d07eff05f952582e569c76844c74678014fe21 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 5 Jul 2014 15:23:38 -0700 Subject: [PATCH 310/488] remove extra import --- Tests/32bit_segfault_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/32bit_segfault_test.py b/Tests/32bit_segfault_test.py index 06fba80e1..958bc6b3c 100644 --- a/Tests/32bit_segfault_test.py +++ b/Tests/32bit_segfault_test.py @@ -1,10 +1,7 @@ #!/usr/bin/env python -import helper - from PIL import Image - im = Image.new('L', (999999, 999999), 0) From 691671624c10cd23a3b48a2942998e80cae2c34e Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 6 Jul 2014 01:47:30 +0300 Subject: [PATCH 311/488] Test _util.py --- Tests/test_util.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Tests/test_util.py diff --git a/Tests/test_util.py b/Tests/test_util.py new file mode 100644 index 000000000..8cf73f47f --- /dev/null +++ b/Tests/test_util.py @@ -0,0 +1,80 @@ +from helper import unittest, PillowTestCase, tearDownModule + +from PIL import _util + + +class TestUtil(PillowTestCase): + + def test_is_string_type(self): + # Arrange + text = "abc" + + # Act + it_is = _util.isStringType(text) + + # Assert + self.assertTrue(it_is) + + def test_is_not_string_type(self): + # Arrange + integer = 123 + + # Act + it_is_not = _util.isStringType(integer) + + # Assert + self.assertFalse(it_is_not) + + def test_is_path(self): + # Arrange + text = "abc" + + # Act + it_is = _util.isStringType(text) + + # Assert + self.assertTrue(it_is) + + def test_is_not_path(self): + # Arrange + integer = 123 + + # Act + it_is_not = _util.isPath(integer) + + # Assert + self.assertFalse(it_is_not) + + def test_is_directory(self): + # Arrange + directory = "Tests" + + # Act + it_is = _util.isDirectory(directory) + + # Assert + self.assertTrue(it_is) + + def test_is_not_directory(self): + # Arrange + text = "abc" + + # Act + it_is_not = _util.isDirectory(text) + + # Assert + self.assertFalse(it_is_not) + + def test_deferred_error(self): + # Arrange + + # Act + thing = _util.deferred_error(ValueError("Some error text")) + + # Assert + self.assertRaises(ValueError, lambda: thing.some_attr) + +if __name__ == '__main__': + unittest.main() + +# End of file From 302978e4644269f87f1259458819e4d59efb4117 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 6 Jul 2014 01:50:24 +0300 Subject: [PATCH 312/488] flake8 --- PIL/_util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PIL/_util.py b/PIL/_util.py index eb5c2c242..51c6f6887 100644 --- a/PIL/_util.py +++ b/PIL/_util.py @@ -3,20 +3,25 @@ import os if bytes is str: def isStringType(t): return isinstance(t, basestring) + def isPath(f): return isinstance(f, basestring) else: def isStringType(t): return isinstance(t, str) + def isPath(f): return isinstance(f, (bytes, str)) + # Checks if an object is a string, and that it points to a directory. def isDirectory(f): return isPath(f) and os.path.isdir(f) + class deferred_error(object): def __init__(self, ex): self.ex = ex + def __getattr__(self, elt): raise self.ex From 60628c77b356d0617932887453c3783307aa682a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 7 Jul 2014 14:42:46 +0900 Subject: [PATCH 313/488] Fix return value of FreeTypeFont.textsize() does not include font offsets --- PIL/ImageFont.py | 3 ++- Tests/images/rectangle_surrounding_text.png | Bin 0 -> 1520 bytes Tests/test_imagefont.py | 16 +++++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 Tests/images/rectangle_surrounding_text.png diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py index 18d09b871..34fe510bd 100644 --- a/PIL/ImageFont.py +++ b/PIL/ImageFont.py @@ -140,7 +140,8 @@ class FreeTypeFont: return self.font.ascent, self.font.descent def getsize(self, text): - return self.font.getsize(text)[0] + size, offset = self.font.getsize(text) + return (size[0] + offset[0], size[1] + offset[1]) def getoffset(self, text): return self.font.getsize(text)[1] diff --git a/Tests/images/rectangle_surrounding_text.png b/Tests/images/rectangle_surrounding_text.png new file mode 100644 index 0000000000000000000000000000000000000000..2b75a5e9c7ae1fc828125e2903ea6136d52cd2eb GIT binary patch literal 1520 zcmcJP`#;lr9LFUc^q`wdij?CJ=9ZS_vYaU6tPRtWVJ^AldZr!ruxWKdE)Pjf?&QvN zNG{8aC6YoKF>^g5cQYojG0wO1!}$-+^M}vp{nPvPcs$?l$2-vx36no~@}Pu-guK1o z6_kX;K7F9}m5~JAx?9;95)ub!_E#=nC*<-*J?)&sRo<*cnFZk8AH+jkW%~Cbo*j=o zY6p(}yl73=wbn~4QEk>q35!Hucl04A*eVmysz`d4{gEP;qDZ=5uddV(Lf46i7WDOQ zH>;%7nnjtq&XB7=-nU!^!fW0HR z1ixaXWavJxCbw8^IVnlhN8w&T@QJ4>C=VB7m22SF48~Z9X+T)eP3qg4dt9!!uHTs> z2_5Z5Wn3=zfIoIC^BdUI^rO|IQx?jH)XgG(ImRFf1)h2aD0i2lHYyd{6yc|lm3Hc9 z=?lB_hN}dlFolB}V_{J^9F7DuW)jw>_=8F(g5Emn;1~K!<9s`h)spzK%$V)QuyOhp z!O4l$)z!r;aG+WjI_fB5mR-;6H{rxKG&IzBGV`;uMZ)y}80_IYpATMwK=Ztnt+i?O zR2UjyTO>l}TV^c|F(&z`U~c=v!~F5)xD;b!W0Ph9t>c8yM#*B)%({?T zRpp$G!eaA#{nv#gXJ_Z|2}WgQWmdrI*49PDYyCi`T4-3<%F0T0MTPOnl(@LKa%7%J zw6T;AB|h;VLP0S|R~;-sk2!Kn*aT3Q;Y&??mjfmGGhLIMMgv#$C1`5_R95q2qisQ^lx#hcSYNBxae z040mRUqlvM%9~FV5G-M->Vr>Ca17|O(^unpKL{zJ#r4>He>QU>n@-%b= zG5qDr7Y?VAy;S@lx@C7^pnR^cz#;p5(W_U2+1?!M1i%cfzJ9Ff&fJ^(`>hjZXJ@HY zDmchqSNf-)BA18LCg`8?EZ8j8Q3cY@7OvbkT>KURi=FuP4d0)8R45dR#bWD(wzjr(kjw1c9Fx3i5*(g9 z$?scMd#PhG5E~jAYMNuPTe1eZbZu;8B$_wUkQtLUHa51zuAwzHmQjKtBfozxv;${GxF8TIR4Tdn1`2R=+|WHi7Rw-(czOdKckjh%xFw6ikZZ!oB>Rolx1A(CGsogV4oXhsqy1Mo545KvKa(h&?L%yYb z&5)!KjQzrOdL#NYH2OLQL)A%o=YzYb7R8@HP#T+?i6jz(!LV#GI};pxcyV|*lVYMG zXFJ911KW}xBV?RBJj6S~^~6M3G#Xf!YHSPLyFETL_Ab+Ew(Oh;VU}_WZ|(u$={d=t zWTMeLKEI}_3O@ZJm^Ny4cSj}9R7Gxr{s(9fqp7KRPT{+dv^Mjky4%-e{57tfzuDK* zLui>eAMj}&Z>~F&o6!n|LV-KrKbfrEV@oPn-HYcd{J5|GBlk*ItTyw)`%x9)**{|J MuOhEhTYLTa52+5~l>h($ literal 0 HcmV?d00001 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 927c80bee..2fa679404 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -71,11 +71,25 @@ try: self.assert_image_equal(img_path, img_filelike) self._clean() + def test_textsize_equal(self): + im = Image.new(mode='RGB', size=(300, 100)) + draw = ImageDraw.Draw(im) + ttf = ImageFont.truetype(font_path, font_size) + + txt = "Hello World!" + size = draw.textsize(txt, ttf) + draw.text((10, 10), txt, font=ttf) + draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) + + target = 'Tests/images/rectangle_surrounding_text.png' + target_img = Image.open(target) + self.assert_image_equal(im, target_img) + def test_render_multiline(self): im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) ttf = ImageFont.truetype(font_path, font_size) - line_spacing = draw.textsize('A', font=ttf)[1] + 8 + line_spacing = draw.textsize('A', font=ttf)[1] + 4 lines = ['hey you', 'you are awesome', 'this looks awkward'] y = 0 for line in lines: From 487bf172fb6586a5868e52a6d589e8e154ef81c0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 7 Jul 2014 19:24:26 +0300 Subject: [PATCH 314/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0bf1a7ee5..610764b0b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ Changelog (Pillow) - Test PalmImagePlugin and method to skip known bad tests [hugovk, wiredfool] + +- Added class checking to Image __eq__ function + [radarhere, hugovk] 2.5.0 (2014-07-01) From 7dc2975cea033c83cd9a0803d3424d3a4a937492 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 7 Jul 2014 19:27:29 +0300 Subject: [PATCH 315/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 610764b0b..4cf38ea20 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,12 +4,15 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ -- Test PalmImagePlugin and method to skip known bad tests - [hugovk, wiredfool] - -- Added class checking to Image __eq__ function +- 32bit mult overflow fix #782 + [wiredfool] + +- Added class checking to Image __eq__ function #775 [radarhere, hugovk] +- Test PalmImagePlugin and method to skip known bad tests #776 + [hugovk, wiredfool] + 2.5.0 (2014-07-01) ------------------ From cf04a9a0d2617d644a5ff5de60c064d2d7a71461 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 7 Jul 2014 20:03:50 +0300 Subject: [PATCH 316/488] Remove unused tearDownModule --- Tests/helper.py | 29 +++++++++++++++-------------- Tests/test_000_sanity.py | 2 +- Tests/test_bmp_reference.py | 2 +- Tests/test_cffi.py | 2 +- Tests/test_decompression_bomb.py | 2 +- Tests/test_file_bmp.py | 2 +- Tests/test_file_eps.py | 2 +- Tests/test_file_fli.py | 2 +- Tests/test_file_gif.py | 2 +- Tests/test_file_icns.py | 2 +- Tests/test_file_ico.py | 2 +- Tests/test_file_jpeg.py | 2 +- Tests/test_file_jpeg2k.py | 2 +- Tests/test_file_libtiff.py | 2 +- Tests/test_file_libtiff_small.py | 2 +- Tests/test_file_msp.py | 2 +- Tests/test_file_palm.py | 2 +- Tests/test_file_pcx.py | 2 +- Tests/test_file_pdf.py | 2 +- Tests/test_file_png.py | 2 +- Tests/test_file_ppm.py | 2 +- Tests/test_file_psd.py | 2 +- Tests/test_file_spider.py | 2 +- Tests/test_file_tar.py | 2 +- Tests/test_file_tiff.py | 2 +- Tests/test_file_tiff_metadata.py | 2 +- Tests/test_file_webp.py | 2 +- Tests/test_file_webp_alpha.py | 2 +- Tests/test_file_webp_lossless.py | 2 +- Tests/test_file_webp_metadata.py | 2 +- Tests/test_file_xbm.py | 2 +- Tests/test_file_xpm.py | 2 +- Tests/test_font_bdf.py | 2 +- Tests/test_font_pcf.py | 2 +- Tests/test_format_lab.py | 2 +- Tests/test_image.py | 2 +- Tests/test_image_array.py | 2 +- Tests/test_image_convert.py | 2 +- Tests/test_image_copy.py | 2 +- Tests/test_image_crop.py | 2 +- Tests/test_image_draft.py | 2 +- Tests/test_image_filter.py | 2 +- Tests/test_image_frombytes.py | 2 +- Tests/test_image_getbands.py | 2 +- Tests/test_image_getbbox.py | 2 +- Tests/test_image_getcolors.py | 2 +- Tests/test_image_getdata.py | 2 +- Tests/test_image_getextrema.py | 2 +- Tests/test_image_getim.py | 2 +- Tests/test_image_getpalette.py | 2 +- Tests/test_image_getpixel.py | 2 +- Tests/test_image_getprojection.py | 2 +- Tests/test_image_histogram.py | 2 +- Tests/test_image_load.py | 2 +- Tests/test_image_mode.py | 2 +- Tests/test_image_offset.py | 2 +- Tests/test_image_point.py | 2 +- Tests/test_image_putalpha.py | 2 +- Tests/test_image_putdata.py | 2 +- Tests/test_image_putpalette.py | 2 +- Tests/test_image_putpixel.py | 2 +- Tests/test_image_quantize.py | 2 +- Tests/test_image_resize.py | 2 +- Tests/test_image_rotate.py | 2 +- Tests/test_image_split.py | 2 +- Tests/test_image_thumbnail.py | 2 +- Tests/test_image_tobitmap.py | 2 +- Tests/test_image_transform.py | 2 +- Tests/test_image_transpose.py | 2 +- Tests/test_imagechops.py | 2 +- Tests/test_imagecms.py | 2 +- Tests/test_imagecolor.py | 2 +- Tests/test_imagedraw.py | 2 +- Tests/test_imageenhance.py | 2 +- Tests/test_imagefile.py | 2 +- Tests/test_imagefileio.py | 2 +- Tests/test_imagefilter.py | 2 +- Tests/test_imagefont.py | 2 +- Tests/test_imagegrab.py | 2 +- Tests/test_imagemath.py | 2 +- Tests/test_imagemode.py | 2 +- Tests/test_imageops.py | 2 +- Tests/test_imageops_usm.py | 2 +- Tests/test_imagepalette.py | 2 +- Tests/test_imagepath.py | 2 +- Tests/test_imageqt.py | 2 +- Tests/test_imagesequence.py | 2 +- Tests/test_imageshow.py | 2 +- Tests/test_imagestat.py | 2 +- Tests/test_imagetk.py | 2 +- Tests/test_imagetransform.py | 2 +- Tests/test_imagewin.py | 2 +- Tests/test_lib_image.py | 2 +- Tests/test_lib_pack.py | 2 +- Tests/test_locale.py | 2 +- Tests/test_mode_i16.py | 2 +- Tests/test_numpy.py | 2 +- Tests/test_olefileio.py | 2 +- Tests/test_pickle.py | 2 +- Tests/test_shell_injection.py | 2 +- 100 files changed, 114 insertions(+), 113 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 7f0aaa73c..c00e105e4 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,22 +5,19 @@ from __future__ import print_function import sys import tempfile import os -import glob if sys.version_info[:2] <= (2, 6): import unittest2 as unittest else: import unittest -def tearDownModule(): - #remove me later - pass class PillowTestCase(unittest.TestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) - self.currentResult = None # holds last result object passed to run method + # holds last result object passed to run method: + self.currentResult = None def run(self, result=None): self.currentResult = result # remember result for use later @@ -40,7 +37,7 @@ class PillowTestCase(unittest.TestCase): except OSError: pass # report? else: - print("=== orphaned temp file: %s" %path) + print("=== orphaned temp file: %s" % path) def assert_almost_equal(self, a, b, msg=None, eps=1e-6): self.assertLess( @@ -134,7 +131,7 @@ class PillowTestCase(unittest.TestCase): if platform is not None: skip = sys.platform.startswith(platform) if travis is not None: - skip = skip and (travis == bool(os.environ.get('TRAVIS',False))) + skip = skip and (travis == bool(os.environ.get('TRAVIS', False))) if skip: self.skipTest(msg or "Known Bad Test") @@ -142,8 +139,8 @@ class PillowTestCase(unittest.TestCase): assert template[:5] in ("temp.", "temp_") (fd, path) = tempfile.mkstemp(template[4:], template[:4]) os.close(fd) - - self.addCleanup(self.delete_tempfile, path) + + self.addCleanup(self.delete_tempfile, path) return path def open_withImagemagick(self, f): @@ -155,8 +152,8 @@ class PillowTestCase(unittest.TestCase): from PIL import Image return Image.open(outfile) raise IOError() - - + + # helpers import sys @@ -210,17 +207,21 @@ def command_succeeds(cmd): return False return True + def djpeg_available(): return command_succeeds(['djpeg', '--help']) + def cjpeg_available(): return command_succeeds(['cjpeg', '--help']) + def netpbm_available(): - return command_succeeds(["ppmquant", "--help"]) and \ - command_succeeds(["ppmtogif", "--help"]) + return (command_succeeds(["ppmquant", "--help"]) and + command_succeeds(["ppmtogif", "--help"])) + def imagemagick_available(): return command_succeeds(['convert', '-version']) - + # End of file diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index 1ad76cc50..22e582ec3 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase import PIL import PIL.Image diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index c8d93983b..b45ea76f6 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image import os diff --git a/Tests/test_cffi.py b/Tests/test_cffi.py index 8ff4e817f..b9f99976d 100644 --- a/Tests/test_cffi.py +++ b/Tests/test_cffi.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena try: import cffi diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 4c09bd9e3..0803732ce 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 2870aba04..e04f3642c 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image import io diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 6a1a1b5e2..0ca4249a3 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image, EpsImagePlugin import io diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index a98a80b78..0c1d6e36a 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index e31779df0..84f34efb3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, netpbm_available +from helper import unittest, PillowTestCase, lena, netpbm_available from PIL import Image from PIL import GifImagePlugin diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index f19eb16b7..99f6da9e3 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 165d10225..c3bf7a992 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 283c48eb7..69c07d2dc 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, py3 +from helper import unittest, PillowTestCase, lena, py3 from helper import djpeg_available, cjpeg_available import random diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 23564c434..a0e7dfb53 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from io import BytesIO diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 1afeee488..60eea8b3b 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, py3 +from helper import unittest, PillowTestCase, lena, py3 import os diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index acc2390c9..043ecaf3f 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -1,4 +1,4 @@ -from helper import unittest, tearDownModule +from helper import unittest from PIL import Image diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 2444879d1..a64faad10 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index c1947ff37..388df0237 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, imagemagick_available +from helper import unittest, PillowTestCase, lena, imagemagick_available import os.path diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index d0800e203..f278bd91d 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 089168393..689302bb5 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena import os.path diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 145eff327..de96fdf3e 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from io import BytesIO diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index d9e4e0674..e1f1537d2 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 007646901..ee903ce5c 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index e0731ca8c..622bfd624 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import SpiderImagePlugin diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 7e36f35fc..7010973ce 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image, TarIO diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index ed3d1e9cd..72156bb39 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, py3 +from helper import unittest, PillowTestCase, lena, py3 from PIL import Image diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 2019f3455..e0805b525 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image, TiffImagePlugin, TiffTags diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 1eeea57d3..ffaf7c673 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 0df3143bb..5f8f653cf 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 9f8e339de..662ad1117 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 2470f4c49..6aadf9c7e 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index d520ef460..02aec70b1 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index e6e750298..d79f5fbda 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index ce5a371e0..0df8e866b 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import FontFile, BdfFontFile diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 8c4c04cd4..5e9e02c8c 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image, FontFile, PcfFontFile from PIL import ImageFont, ImageDraw diff --git a/Tests/test_format_lab.py b/Tests/test_format_lab.py index 188b0d1fa..53468db5f 100644 --- a/Tests/test_format_lab.py +++ b/Tests/test_format_lab.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_image.py b/Tests/test_image.py index e41447e42..174964ce7 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index dce2fa106..a0f5f29e1 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1415bae3a..01a80732b 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py index 205118e47..a7882db94 100644 --- a/Tests/test_image_copy.py +++ b/Tests/test_image_copy.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index f7ea48c95..da93fe7c8 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 252e60376..a76b8d266 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, fromstring, tostring +from helper import unittest, PillowTestCase, fromstring, tostring from PIL import Image diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 8c04ebb1d..4a85b0a2e 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageFilter diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py index abba18852..aad8046a1 100644 --- a/Tests/test_image_frombytes.py +++ b/Tests/test_image_frombytes.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_getbands.py b/Tests/test_image_getbands.py index 6aadaa502..e803abb02 100644 --- a/Tests/test_image_getbands.py +++ b/Tests/test_image_getbands.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py index f89dcf7ca..8d78195bd 100644 --- a/Tests/test_image_getbbox.py +++ b/Tests/test_image_getbbox.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_getcolors.py b/Tests/test_image_getcolors.py index 7011c3443..d3e5a4989 100644 --- a/Tests/test_image_getcolors.py +++ b/Tests/test_image_getcolors.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageGetColors(PillowTestCase): diff --git a/Tests/test_image_getdata.py b/Tests/test_image_getdata.py index 71416c4b9..ff6659595 100644 --- a/Tests/test_image_getdata.py +++ b/Tests/test_image_getdata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageGetData(PillowTestCase): diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 7d896c821..af7f7698a 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageGetExtrema(PillowTestCase): diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index 6141877cd..d498d3923 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, py3 +from helper import unittest, PillowTestCase, lena, py3 class TestImageGetIm(PillowTestCase): diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index 8b6804a5a..0c399c432 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageGetPalette(PillowTestCase): diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py index 20be3bdec..965233f94 100644 --- a/Tests/test_image_getpixel.py +++ b/Tests/test_image_getpixel.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_image_getprojection.py b/Tests/test_image_getprojection.py index 8c340847c..262a21d4b 100644 --- a/Tests/test_image_getprojection.py +++ b/Tests/test_image_getprojection.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py index 6fd203758..70f78a1fb 100644 --- a/Tests/test_image_histogram.py +++ b/Tests/test_image_histogram.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageHistogram(PillowTestCase): diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index 14cb76fdb..786cd6ad8 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index ba5e0810a..25c35c607 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_offset.py b/Tests/test_image_offset.py index 1b45fec4a..09f12266f 100644 --- a/Tests/test_image_offset.py +++ b/Tests/test_image_offset.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageOffset(PillowTestCase): diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 1f3aaf446..7b6cd4fc7 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena import sys diff --git a/Tests/test_image_putalpha.py b/Tests/test_image_putalpha.py index bb36b335e..85c7ac262 100644 --- a/Tests/test_image_putalpha.py +++ b/Tests/test_image_putalpha.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index d792adfe6..c7c3115aa 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena import sys diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 26ad09800..a77c1e565 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import ImagePalette diff --git a/Tests/test_image_putpixel.py b/Tests/test_image_putpixel.py index 1afc013c0..a7f5dc2bb 100644 --- a/Tests/test_image_putpixel.py +++ b/Tests/test_image_putpixel.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 63fe0cb21..2cbdac225 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index a200b17b4..6c9932e45 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageResize(PillowTestCase): diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index bb24ddf4f..531fdd63f 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageRotate(PillowTestCase): diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index 284acd87c..343f4bf8e 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 6b33da318..ee49be43e 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena class TestImageThumbnail(PillowTestCase): diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py index 93f01c9de..56b5ef001 100644 --- a/Tests/test_image_tobitmap.py +++ b/Tests/test_image_tobitmap.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, fromstring +from helper import unittest, PillowTestCase, lena, fromstring class TestImageToBitmap(PillowTestCase): diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index b36344416..1873ee9a4 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index ec83aa3a6..f13e54ee7 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index fe377f864..552314fd1 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageChops diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index f3f0791e5..152241f90 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index fce64876b..5d8944852 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageColor diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 4610e2b0b..b632da73b 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageColor diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index eec26d768..433c49cf6 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageEnhance diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 849767195..d7f7f2a56 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, fromstring, tostring +from helper import unittest, PillowTestCase, lena, fromstring, tostring from io import BytesIO diff --git a/Tests/test_imagefileio.py b/Tests/test_imagefileio.py index 791207dca..32ee0bc5e 100644 --- a/Tests/test_imagefileio.py +++ b/Tests/test_imagefileio.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena, tostring +from helper import unittest, PillowTestCase, lena, tostring from PIL import Image from PIL import ImageFileIO diff --git a/Tests/test_imagefilter.py b/Tests/test_imagefilter.py index 3dcb1d14f..f7edb409a 100644 --- a/Tests/test_imagefilter.py +++ b/Tests/test_imagefilter.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import ImageFilter diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 927c80bee..17cb38cc2 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageDraw diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index a6c50fb31..2275d34a1 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase try: from PIL import ImageGrab diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 35d75dbbd..17d43d25a 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageMath diff --git a/Tests/test_imagemode.py b/Tests/test_imagemode.py index 7febc697e..2c5730d74 100644 --- a/Tests/test_imagemode.py +++ b/Tests/test_imagemode.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import ImageMode diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 299a7c618..a4a94ca4d 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import ImageOps diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 486b201ab..be7a669da 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageOps diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 3ee7ee869..be82f4dcb 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import ImagePalette diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index c293e4225..cd221b5ca 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import ImagePath diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 549fc7fd6..fd50bf320 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena try: from PIL import ImageQt diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 7f8838207..fd10e5989 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import ImageSequence diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 08b3ff183..e94ae2d0a 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageShow diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 7eded56cf..4d30ff023 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageStat diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index b868096b2..87a07e288 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase class TestImageTk(PillowTestCase): diff --git a/Tests/test_imagetransform.py b/Tests/test_imagetransform.py index dfffafe54..f5741df32 100644 --- a/Tests/test_imagetransform.py +++ b/Tests/test_imagetransform.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image from PIL import ImageTransform diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index f22babbb3..69dbdbe82 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import ImageWin diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index c7ea4c701..e0a903b00 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index c8ed39c40..102835b58 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, py3 +from helper import unittest, PillowTestCase, py3 from PIL import Image diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 0465fb207..9ef136bf9 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index d8e205b66..b7dc76fb4 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index c3c0f7e90..07c3e0c21 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule, lena +from helper import unittest, PillowTestCase, lena from PIL import Image diff --git a/Tests/test_olefileio.py b/Tests/test_olefileio.py index f31302db1..1cff273a1 100644 --- a/Tests/test_olefileio.py +++ b/Tests/test_olefileio.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase import datetime diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 304baf964..eae5eb671 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import Image diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index eff03fd59..ef80bfc98 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from helper import djpeg_available, cjpeg_available, netpbm_available import sys From 8d5a1c2a19d5ac4e1047b052cc6a1fbdd703f398 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Jul 2014 11:21:46 -0700 Subject: [PATCH 317/488] Renamed to avoid test runner --- Tests/{32bit_segfault_test.py => 32bit_segfault_check.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/{32bit_segfault_test.py => 32bit_segfault_check.py} (100%) diff --git a/Tests/32bit_segfault_test.py b/Tests/32bit_segfault_check.py similarity index 100% rename from Tests/32bit_segfault_test.py rename to Tests/32bit_segfault_check.py From 0a160e44d8f345d89527995bf565101a4f89fba8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Jul 2014 11:22:11 -0700 Subject: [PATCH 318/488] Guard to prevent 64 bit machines from crashing --- Tests/32bit_segfault_check.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/32bit_segfault_check.py b/Tests/32bit_segfault_check.py index 958bc6b3c..822d524fd 100644 --- a/Tests/32bit_segfault_check.py +++ b/Tests/32bit_segfault_check.py @@ -1,7 +1,10 @@ #!/usr/bin/env python from PIL import Image +import sys -im = Image.new('L', (999999, 999999), 0) + +if sys.maxsize < 2**32: + im = Image.new('L', (999999, 999999), 0) From 925277cbfcf881f04a5ace69f88a8b3f4ff5b992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20J=C3=B8rgen=20Solberg?= Date: Mon, 7 Jul 2014 20:46:54 +0200 Subject: [PATCH 319/488] ensure that the prevous frame was loaded when seek()ing --- PIL/GifImagePlugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index cb96b9c95..4107c6ba3 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -101,6 +101,10 @@ class GifImageFile(ImageFile.ImageFile): self.__fp.seek(self.__rewind) self._prev_im = None self.disposal_method = 0 + else: + # ensure that the previous frame was loaded + if not self.im: + self.load() if frame != self.__frame + 1: raise ValueError("cannot seek to frame %d" % frame) From 5fa2794386f4cf44997e19cf2d89c156349fd381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20J=C3=B8rgen=20Solberg?= Date: Mon, 7 Jul 2014 20:47:18 +0200 Subject: [PATCH 320/488] Tests dispose and transparency for animated gifs --- Tests/images/dispose_bgnd.gif | Bin 0 -> 1450 bytes Tests/images/dispose_none.gif | Bin 0 -> 1450 bytes Tests/images/dispose_prev.gif | Bin 0 -> 1450 bytes Tests/images/iss634.gif | Bin 0 -> 277517 bytes Tests/test_file_gif.py | 44 ++++++++++++++++++++++++++++++++++ 5 files changed, 44 insertions(+) create mode 100644 Tests/images/dispose_bgnd.gif create mode 100644 Tests/images/dispose_none.gif create mode 100644 Tests/images/dispose_prev.gif create mode 100644 Tests/images/iss634.gif diff --git a/Tests/images/dispose_bgnd.gif b/Tests/images/dispose_bgnd.gif new file mode 100644 index 0000000000000000000000000000000000000000..d68504010fe1248b656d15ee84172da4ab402219 GIT binary patch literal 1450 zcmYk*dpOg390%~Drc~ z%-Iz$W>&3$+SY`PXz7U!Q~KtU``ig>i5p6lQFeuJ`lA3cRA6#wv@Ry}Ug$M`1ATit zD~#I=yMKnAT-&ZJ$YspM9QezeH({0VrMhWTk~4r(;`mLbmn?bo;qmVwo`~9zpG-_h zFKf7ius2&!c?XDt*JoVTN`+qabKVPR;>H7qxS>JZ zS{xAPF&dTCfl@IHhs)_=)g4RW!5?+iVYnG2kwPLedNL7qxD@Eh;S16V~VNT4n;G|)uo{&rKD(KfUe z&5lTEg%17+3-@h)ns#0%`3}8W((p5={-9c=B`3GFV=(@*3fXf&&VFT~(QzRP6)fET zh)dCtcQbSLk2NDJxuJ{_A_8rcY$Fq>#ZItzyh3mh9uXgN#UkFYFv5lvinWI+U{j2+ zF&bCT<|k+0tKfF%N-4J%mGwAd^Go`BesQHG5wj**d2fCnwEO!tB zJr0)uTdQAxJrI7$w%fWBHuHR}J~Nhxc%}y#B8v6l%w?%9R=S!9ZPa>3p_kRvNz3wn zwEV@UOA6j7*2yCkcG&?~RT+0GAVo?|Rgk-Fg*hQkjw4Vk&3?X$29tB+@HBLEmWONZ z%`lo4BBC&fUP7hBA_$3DH!JhXC}@WC9d>Cvvw%TpF)WE~z&A0ud94rH^Q$;r{_%IZ z%T*XO-f(?C;Xb9eu5(0_@oxD&v{WMYa+wbIzJ$Uu`8e$iC$@64D(%#%wcMK4$7WC zk>@27kloi$d%v1mH0D#a1;?;ytgSS6m5%8Br*obLI0_2*m|$G+?-w8)>slY^TG-%c zIuH$CW2DK8vFeQL2WtXt@k&!g1s6-y*>!W=A4=|ozGGpEiueZSB5kMH;M`RDt3-XB|gjIoJd01yDY0{~U( zZu%1eVjBz&00027EA4FK=4xeuwLu#o#XzDbebEuMimLl|a`I{)ugTplZ=23;o$VIN zQReN6molqXL2c`TMzr+grYU{v$$jpmw8Rai$|$=+IQ>xo8Ok>~G*%ZAdN1@EuYoQU zW`%K^VfW9llWW_R`MHewm;-;AcTQR*e5r2QlH?4clsJA<=_Sh^{doL)h$o^pVE z=Wmn1D#s0-Ui&9MaAijvdF4I&L+)|&9`DFVD_Y9OQCX|)M%5E)(Vo{_^8UDZnvnpM{B_YW^lD`+G`pG#(WAM&TRuXz&`aiq zmaf4@?7V`s!h=s%Ke=U=`1uRea5-}y@Gq*qI3NY6i#B(Z&wO89~_-=vMu|xLk2gc-4iydAZps7xUhtOEKoYchPx|{i^GBqKz_%7KGP?wdF zm>F8^&A{0DZ1Nd(OX-9^>gqgYgi(etm}28RqdxH56ytI49RJON-s}={d4=Ed zaK>4%##7PVaNhd3iP%>2{4_D8jZ{LN76+-;Z@8%S$jOWH_brF|ntd8FLiQY#vtM0obX<%=1q*~9 zaVa|TZf4H@v1Vi?HE*! zM&s(){N(I=72FP8DdpCpvR-Fwe#t=ZFYeYCMn+wHvIb?693M{hT~I`h&#>2%N>M3 zPrxO>*6P<^4~Ac|?Xm8H%|0Km&x|D^p6Nk`iDLaYb6IMOm98d28?}*9=w&s1(z1L2 zEq}4;l7csib@E7sU3LIgRmPnPNRd)g736MPVNQsX;|LT>v!Ab`!Q|XHJPjS4<>8up zGmNH%h$u{=mryCO2ts1k&C0wo3YsB(hg}-aEMO2?3`=4g@J&o^Uh9MQ{3=ejfBc=E zauo)xbEJNNaG%mw*EOoicsRxz-aj^lpN{QMn?sB(KVI+~G#HxC^KJi)x#=eBH2bp9 zX=RYYHA9Qg%2OfW9^_Y07Y4XqD!Eo|^J z9f*dnG1BD4cy-40gEfJ+c%|v0f)&?=(g~fyx})wd6(zXE83zYfmI*6mhgJt9n(-B7 b%E*yZ1O;iw&90#_6P@8>Mz6lO0)Xk?ykLfe literal 0 HcmV?d00001 diff --git a/Tests/images/dispose_prev.gif b/Tests/images/dispose_prev.gif new file mode 100644 index 0000000000000000000000000000000000000000..0de7760e98e27e622490a8221b7f4074d6853fc8 GIT binary patch literal 1450 zcmYk*dpOg390%~6ScX` zCD&%;iQ^%nVu>V9n1jVBok%*ItR$R2 z0b&~r284y!lXkXobG5R-+Mo@PVjy8lU(^>31N(M!@@gNi$=xk)o6c^X?cR~2%-a<& zWmc_%+SUb)Xz9sKQ~K7E`<;{05;v48qwEUd^hW_?DBtAJSY1r$z0hmC2KvrUR#;~< z?EV>cQf<33KbJ8dbKoy??xa=xm+Gc1NzNcjiQ_kwUb5`bPawR9cp_>;eljshd0Epr z1bed$m3M$RczwoYtybt&Kj*%HCTyNz2ZcJCma!SbVU(R6Mg4A@%i;JOIIDZ)`P)RW z%5g)d*ZxTlT-gyvUU`rHkbB&`$2&68ikAFwRMu*{QT2pcwC6RKygx49PLZpqymF41 zHU&ayhrl6eiEaF-PsnL_FpgTQqqX1xGq_|Ogh!+u&90_G^k{DHmXDAv^pZM5OV?l{ zc3weR;lU@XpWHG_`1}QGxSY8U5TeQm*#LcUKnhS79l)M6)C96=$th=7QTxndD#SQuf$3dP#P6tKxg*cgqg zXY-S??^Sem=t?QK7M1lnWAjS}dVg`ZzA!TC;*&Kflj!(xvhRW-a(sqmT{$CTN#HNN z=B^5^c!bpmJQl^fmx6wULH~C!2hjgH2>&nK)^7;vEp^}GBDO1ZFbBG!{49460zCnj z09&hHe?1s}$+pM33pV?Fygn08LOj!h43otAaptnr7Asv%gf?m;qtMH0`lMy~09yWH z(@D4 z8h50AfOwzMSJySF$#^)%8{R)QMVQ9-r_CY8mLD(p4H^v1=lQn(#@uw1b((!y=(IA( z^hTyQrusrQbA74OFV-groU8rqnF?%UHyY@>&`1HNzhb5Q6Bm|fzxHu*@{F5D=LQ^p z^wRb(u02ckhD-WXb+C%6%jdl)Q#Fy{X0{ki{MxsyJ7sGrPBXgMAo`bUIP3C#Y z1Z4LQ(B7}57LEH|LL5k0gn6v9wrzU{QCt+$A;Dix)wJ0nGQt5 z*BEK?V!S%z`oWq&TY}PbQNfDqLg|D~Vck*pmx>ad#Tf?&SC)w@WrtP=B$^2oWy;8r YQ$z)6$IY&xF%zBPV@9vOxB`Ib-{1y@KmY&$ literal 0 HcmV?d00001 diff --git a/Tests/images/iss634.gif b/Tests/images/iss634.gif new file mode 100644 index 0000000000000000000000000000000000000000..ba4e4566fa40e80bbf63ba5ddf2b42b75b93a2f4 GIT binary patch literal 277517 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%0A2vEb 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 HNDBW4E_Em2 literal 0 HcmV?d00001 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index e31779df0..2aa7f1f3b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -106,6 +106,50 @@ class TestFileGif(PillowTestCase): GifImagePlugin._save_netpbm(img, 0, tempfile) self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) + def test_seek(self): + img = Image.open("Tests/images/dispose_none.gif") + framecount = 0 + try: + while True: + framecount += 1 + img.seek(img.tell() +1) + except EOFError: + assert(framecount == 5) + + def test_dispose_none(self): + img = Image.open("Tests/images/dispose_none.gif") + try: + while True: + img.seek(img.tell() +1) + assert(img.disposal_method == 1) + except EOFError: + pass + + def test_dispose_background(self): + img = Image.open("Tests/images/dispose_bgnd.gif") + try: + while True: + img.seek(img.tell() +1) + assert(img.disposal_method == 2) + except EOFError: + pass + + def test_dispose_previous(self): + img = Image.open("Tests/images/dispose_prev.gif") + try: + while True: + img.seek(img.tell() +1) + assert(img.disposal_method == 3) + except EOFError: + pass + + def test_iss634(self): + img = Image.open("Tests/images/iss634.gif") + # seek to the second frame + img.seek(img.tell() +1) + # all transparent pixels should be replaced with the color from the first frame + assert(img.histogram()[img.info['transparency']] == 0) + if __name__ == '__main__': unittest.main() From add45b494a6a99cdc9084f6b0ef8dafa66b20a8c Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 7 Jul 2014 22:31:20 +0300 Subject: [PATCH 321/488] Extract __main__ section of PIL/ImageFont.py into Scripts/createfontdatachunk.py --- PIL/ImageFont.py | 13 +------------ Scripts/createfontdatachunk.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 Scripts/createfontdatachunk.py diff --git a/PIL/ImageFont.py b/PIL/ImageFont.py index 25993007d..036a42eb9 100644 --- a/PIL/ImageFont.py +++ b/PIL/ImageFont.py @@ -403,15 +403,4 @@ w7IkEbzhVQAAAABJRU5ErkJggg== ''')))) return f - -if __name__ == "__main__": - # create font data chunk for embedding - import base64 - font = "Tests/images/courB08" - print(" f._load_pilfont_data(") - print(" # %s" % os.path.basename(font)) - print(" BytesIO(base64.decodestring(b'''") - base64.encode(open(font + ".pil", "rb"), sys.stdout) - print("''')), Image.open(BytesIO(base64.decodestring(b'''") - base64.encode(open(font + ".pbm", "rb"), sys.stdout) - print("'''))))") +# End of file diff --git a/Scripts/createfontdatachunk.py b/Scripts/createfontdatachunk.py new file mode 100644 index 000000000..0c860701a --- /dev/null +++ b/Scripts/createfontdatachunk.py @@ -0,0 +1,16 @@ +import base64 +import os +import sys + +if __name__ == "__main__": + # create font data chunk for embedding + font = "Tests/images/courB08" + print(" f._load_pilfont_data(") + print(" # %s" % os.path.basename(font)) + print(" BytesIO(base64.decodestring(b'''") + base64.encode(open(font + ".pil", "rb"), sys.stdout) + print("''')), Image.open(BytesIO(base64.decodestring(b'''") + base64.encode(open(font + ".pbm", "rb"), sys.stdout) + print("'''))))") + +# End of file From e24cd3652cc615ba05629f6c8df190259d151064 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 7 Jul 2014 22:34:34 +0300 Subject: [PATCH 322/488] Try travis_retry to avoid spurious pyroma installation failure --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b4b98e784..c9c1e75a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,8 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" - "pip install cffi" - - "pip install coveralls nose pyroma" + - "pip install coveralls nose" + - travis_retry pip install pyroma - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp From 9e5f2f92497d067bfa2be0c6cb6167d133cefcb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20J=C3=B8rgen=20Solberg?= Date: Mon, 7 Jul 2014 21:49:45 +0200 Subject: [PATCH 323/488] use assertEqual for test cases --- Tests/test_file_gif.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 2aa7f1f3b..c5da0910b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -114,14 +114,14 @@ class TestFileGif(PillowTestCase): framecount += 1 img.seek(img.tell() +1) except EOFError: - assert(framecount == 5) + self.assertEqual(framecount, 5) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") try: while True: img.seek(img.tell() +1) - assert(img.disposal_method == 1) + self.assertEqual(img.disposal_method, 1) except EOFError: pass @@ -130,7 +130,7 @@ class TestFileGif(PillowTestCase): try: while True: img.seek(img.tell() +1) - assert(img.disposal_method == 2) + self.assertEqual(img.disposal_method, 2) except EOFError: pass @@ -139,7 +139,7 @@ class TestFileGif(PillowTestCase): try: while True: img.seek(img.tell() +1) - assert(img.disposal_method == 3) + self.assertEqual(img.disposal_method, 3) except EOFError: pass @@ -148,7 +148,7 @@ class TestFileGif(PillowTestCase): # seek to the second frame img.seek(img.tell() +1) # all transparent pixels should be replaced with the color from the first frame - assert(img.histogram()[img.info['transparency']] == 0) + self.assertEqual(img.histogram()[img.info['transparency']], 0) if __name__ == '__main__': From ef3ba5bfafd674929b77061089da0f69dc5c9003 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 7 Jul 2014 23:06:33 +0300 Subject: [PATCH 324/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4cf38ea20..b3a59927b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fix dispose calculations for animated GIFs #765 + [larsjsol] + - 32bit mult overflow fix #782 [wiredfool] From 73223fcb238959986f90635932d67c441d10a448 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 7 Jul 2014 23:40:37 +0300 Subject: [PATCH 325/488] Test isStringType and isPath in similar way to production code --- Tests/test_util.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 8cf73f47f..a87a7833c 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -7,40 +7,41 @@ class TestUtil(PillowTestCase): def test_is_string_type(self): # Arrange - text = "abc" + color = "red" # Act - it_is = _util.isStringType(text) + it_is = _util.isStringType(color) # Assert self.assertTrue(it_is) def test_is_not_string_type(self): # Arrange - integer = 123 + color = (255, 0, 0) # Act - it_is_not = _util.isStringType(integer) + it_is_not = _util.isStringType(color) # Assert self.assertFalse(it_is_not) def test_is_path(self): # Arrange - text = "abc" + fp = "filename.ext" # Act - it_is = _util.isStringType(text) + it_is = _util.isStringType(fp) # Assert self.assertTrue(it_is) def test_is_not_path(self): # Arrange - integer = 123 + filename = self.tempfile("temp.ext") + fp = open(filename, 'w').close() # Act - it_is_not = _util.isPath(integer) + it_is_not = _util.isPath(fp) # Assert self.assertFalse(it_is_not) From 3d71e3fdb22c9cb3f59f05cd474263fbe2dca380 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 8 Jul 2014 07:59:17 +0300 Subject: [PATCH 326/488] Remove unused tearDownModule --- Tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index a87a7833c..a547c6bd6 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, tearDownModule +from helper import unittest, PillowTestCase from PIL import _util From 37e6a57351b5f164f610e2719daa22543244645e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 8 Jul 2014 05:16:12 -0400 Subject: [PATCH 327/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b3a59927b..e32adbad9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fix return value of FreeTypeFont.textsize() does not include font offsets + [tk0miya] + - Fix dispose calculations for animated GIFs #765 [larsjsol] From b5c3eb58307f948932e214dc958de003da8aec8e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 8 Jul 2014 10:37:27 -0700 Subject: [PATCH 328/488] ucase(font_path,font_size) --- Tests/test_imagefont.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index ed3715ed9..ed2439e7c 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -74,7 +74,7 @@ try: def test_textsize_equal(self): im = Image.new(mode='RGB', size=(300, 100)) draw = ImageDraw.Draw(im) - ttf = ImageFont.truetype(font_path, font_size) + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) txt = "Hello World!" size = draw.textsize(txt, ttf) From 33e8480c8f42a1162902525f11a4a84b10122395 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 8 Jul 2014 10:55:56 -0700 Subject: [PATCH 329/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e32adbad9..a51d8b451 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- More tests for ImageFont and _util + [hugovk] - Fix return value of FreeTypeFont.textsize() does not include font offsets [tk0miya] From 1f0d3e9b001528d4588ec5e028cc58d311187c06 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Wed, 9 Jul 2014 10:12:43 -0700 Subject: [PATCH 330/488] Don't install mp_compile if multiprocessing.Pool() fails, or if 1 process is going to be used --- mp_compile.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/mp_compile.py b/mp_compile.py index 71b4089e1..b7c3e7ce4 100644 --- a/mp_compile.py +++ b/mp_compile.py @@ -5,6 +5,11 @@ from multiprocessing import Pool, cpu_count from distutils.ccompiler import CCompiler import os +try: + MAX_PROCS = int(os.environ.get('MAX_CONCURRENCY', cpu_count())) +except: + MAX_PROCS = None + # hideous monkeypatching. but. but. but. def _mp_compile_one(tp): @@ -31,22 +36,27 @@ def _mp_compile(self, sources, output_dir=None, macros=None, output_dir, macros, include_dirs, sources, depends, extra_postargs) cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) - try: - max_procs = int(os.environ.get('MAX_CONCURRENCY', cpu_count())) - except: - max_procs = None - pool = Pool(max_procs) + pool = Pool(MAX_PROCS) try: print ("Building using %d processes" % pool._processes) except: pass - arr = [ - (self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects - ] + arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) + for obj in objects] pool.map_async(_mp_compile_one, arr) pool.close() pool.join() # Return *all* object filenames, not just the ones we just built. return objects -CCompiler.compile = _mp_compile +# explicitly don't enable if environment says 1 processor +if MAX_PROCS != 1: + try: + # bug, only enable if we can make a Pool. see issue #790 and + # http://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing + pool = Pool(2) + CCompiler.compile = _mp_compile + except Exception as msg: + print("Exception installing mp_compile, proceeding without: %s" %msg) +else: + print("Single threaded build, not installing mp_compile: %s processes" %MAX_PROCS) From 919315eb9d55955a1b3cedadcdf111aca7af9762 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Wed, 9 Jul 2014 10:13:02 -0700 Subject: [PATCH 331/488] More agressive make clean --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0637e901f..3dcfe8d13 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,9 @@ pre: clean: python setup.py clean rm PIL/*.so || true - find . -name __pycache__ | xargs rm -r + rm -r build || true + find . -name __pycache__ | xargs rm -r || true + install: python setup.py install From e3911facbc84e2d17f04b190afb2f22091e4430b Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 9 Jul 2014 21:53:13 +0300 Subject: [PATCH 332/488] Test ImageMath's mod, imagemath_equal and imagemath_notequal --- Tests/test_imagemath.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index a03b25cce..562a2f8f6 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -83,6 +83,14 @@ class TestImageMath(PillowTestCase): self.assertEqual(pixel(ImageMath.eval("abs(A)", A=A)), "I 1") self.assertEqual(pixel(ImageMath.eval("abs(B)", B=B)), "I 2") + def test_binary_mod(self): + self.assertEqual(pixel(ImageMath.eval("A%A", A=A)), "I 0") + self.assertEqual(pixel(ImageMath.eval("B%B", B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("A%B", A=A, B=B)), "I 1") + self.assertEqual(pixel(ImageMath.eval("B%A", A=A, B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z%A", A=A, Z=Z)), "I 0") + self.assertEqual(pixel(ImageMath.eval("Z%B", B=B, Z=Z)), "I 0") + def test_bitwise_invert(self): self.assertEqual(pixel(ImageMath.eval("~Z", Z=Z)), "I -1") self.assertEqual(pixel(ImageMath.eval("~A", A=A)), "I -2") @@ -154,6 +162,24 @@ class TestImageMath(PillowTestCase): self.assertEqual(pixel(ImageMath.eval("A>=B", A=A, B=B)), "I 0") self.assertEqual(pixel(ImageMath.eval("B>=A", A=A, B=B)), "I 1") + def test_logical_equal(self): + self.assertEqual(pixel(ImageMath.eval("equal(A, A)", A=A)), "I 1") + self.assertEqual(pixel(ImageMath.eval("equal(B, B)", B=B)), "I 1") + self.assertEqual(pixel(ImageMath.eval("equal(Z, Z)", Z=Z)), "I 1") + self.assertEqual(pixel(ImageMath.eval("equal(A, B)", A=A, B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("equal(B, A)", A=A, B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("equal(A, Z)", A=A, Z=Z)), "I 0") + + def test_logical_not_equal(self): + self.assertEqual(pixel(ImageMath.eval("notequal(A, A)", A=A)), "I 0") + self.assertEqual(pixel(ImageMath.eval("notequal(B, B)", B=B)), "I 0") + self.assertEqual(pixel(ImageMath.eval("notequal(Z, Z)", Z=Z)), "I 0") + self.assertEqual( + pixel(ImageMath.eval("notequal(A, B)", A=A, B=B)), "I 1") + self.assertEqual( + pixel(ImageMath.eval("notequal(B, A)", A=A, B=B)), "I 1") + self.assertEqual( + pixel(ImageMath.eval("notequal(A, Z)", A=A, Z=Z)), "I 1") if __name__ == '__main__': From b12c8b5045c0d4e79ca71fdc03826fe9effe8be8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 9 Jul 2014 13:15:53 -0700 Subject: [PATCH 333/488] Update CHANGES.rst [ci skip] --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a51d8b451..625dcd0bc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,10 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ + +- Fixed install issue if Multiprocessing.Pool is not available + [wiredfool] + - More tests for ImageFont and _util [hugovk] From 1141e636d9af3b2310fb9d70f872dac53cd0a957 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 10 Jul 2014 02:00:26 +0300 Subject: [PATCH 334/488] More tests for Image.py --- Tests/test_image.py | 49 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 174964ce7..c1c2c3d86 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, lena from PIL import Image @@ -55,6 +55,53 @@ class TestImage(PillowTestCase): self.assertFalse(item == None) self.assertFalse(item == num) + def test_expand_x(self): + # Arrange + im = lena() + orig_size = im.size + xmargin = 5 + + # Act + im = im._expand(xmargin) + + # Assert + self.assertEqual(im.size[0], orig_size[0] + 2*xmargin) + self.assertEqual(im.size[1], orig_size[1] + 2*xmargin) + + def test_expand_xy(self): + # Arrange + im = lena() + orig_size = im.size + xmargin = 5 + ymargin = 3 + + # Act + im = im._expand(xmargin, ymargin) + + # Assert + self.assertEqual(im.size[0], orig_size[0] + 2*xmargin) + self.assertEqual(im.size[1], orig_size[1] + 2*ymargin) + + def test_getbands(self): + # Arrange + im = lena() + + # Act + bands = im.getbands() + + # Assert + self.assertEqual(bands, ('R', 'G', 'B')) + + def test_getbbox(self): + # Arrange + im = lena() + + # Act + bbox = im.getbbox() + + # Assert + self.assertEqual(bbox, (0, 0, 128, 128)) + if __name__ == '__main__': unittest.main() From ee29b0812b415cd8d6ee5f92e9827006e6e165ed Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 10 Jul 2014 10:15:10 -0700 Subject: [PATCH 335/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 625dcd0bc..342a99119 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,7 +7,7 @@ Changelog (Pillow) - Fixed install issue if Multiprocessing.Pool is not available [wiredfool] -- More tests for ImageFont and _util +- More tests for ImageFont, ImageMath, and _util [hugovk] - Fix return value of FreeTypeFont.textsize() does not include font offsets From 0b278f33760b29fc597a16189a0c993a87614e60 Mon Sep 17 00:00:00 2001 From: Michael Nagy Date: Sat, 12 Jul 2014 10:44:27 -0500 Subject: [PATCH 336/488] Added docs for previously-undocumented ExifTags module. --- docs/reference/ExifTags.rst | 26 ++++++++++++++++++++++++++ docs/reference/index.rst | 1 + 2 files changed, 27 insertions(+) create mode 100644 docs/reference/ExifTags.rst diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst new file mode 100644 index 000000000..9fc7cd13b --- /dev/null +++ b/docs/reference/ExifTags.rst @@ -0,0 +1,26 @@ +.. py:module:: PIL.ExifTags +.. py:currentmodule:: PIL.ExifTags + +:py:mod:`ExifTags` Module +========================== + +The :py:mod:`ExifTags` module exposes two dictionaries which +provide constants and clear-text names for various well-known EXIF tags. + +.. py:class:: PIL.ExifTags.TAGS + + The TAG dictionary maps 16-bit integer EXIF tag enumerations to + descriptive string names. For instance: + + >>> from PIL.ExifTags import TAGS + >>> TAGS[0x010e] + 'ImageDescription' + +.. py:class:: PIL.ExifTags.GPSTAGS + + The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to + descriptive string names. For instance: + + >>> from PIL.ExifTags import GPSTAGS + >>> GPSTAGS[20] + 'GPSDestLatitude' diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 2d57e37be..563e03acf 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -4,6 +4,7 @@ Reference .. toctree:: :maxdepth: 2 + ExifTags Image ImageChops ImageColor From c9fccf8ba8ce919766070778f7084cba282a95e6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 12 Jul 2014 09:35:38 -0700 Subject: [PATCH 337/488] Reducing priority of exiftags docs --- docs/reference/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 563e03acf..66310e3e7 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -4,7 +4,7 @@ Reference .. toctree:: :maxdepth: 2 - ExifTags + Image ImageChops ImageColor @@ -23,5 +23,6 @@ Reference ImageStat ImageTk ImageWin + ExifTags PSDraw ../PIL From 8edab8b9393a2199fc3e7dcb358d01bb3d11c2f3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 12 Jul 2014 09:37:27 -0700 Subject: [PATCH 338/488] Removed autodoc for ExifTag --- docs/PIL.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/PIL.rst b/docs/PIL.rst index 6726f661f..0a6ccd62a 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -20,14 +20,6 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`ExifTags` Module ----------------------- - -.. automodule:: PIL.ExifTags - :members: - :undoc-members: - :show-inheritance: - :mod:`FontFile` Module ---------------------- From 5d3f8343852ca9c7fefe913f2f1ef1b6d936ab19 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 12 Jul 2014 09:41:34 -0700 Subject: [PATCH 339/488] including ImageMorph autodoc --- docs/PIL.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/PIL.rst b/docs/PIL.rst index 0a6ccd62a..3b4706511 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -78,6 +78,14 @@ can be found here. :undoc-members: :show-inheritance: +:mod:`ImageMorph` Module +------------------------ + +.. automodule:: PIL.ImageMorph + :members: + :undoc-members: + :show-inheritance: + :mod:`ImageShow` Module ----------------------- From b6b36543ceb8d3e7dc9cb5e2b5d5181795eb625e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 12 Jul 2014 09:52:04 -0700 Subject: [PATCH 340/488] Autodoc formatting --- PIL/ImageMorph.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index b24dd134f..3f15621a6 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -15,19 +15,19 @@ LUT_SIZE = 1 << 9 class LutBuilder: """A class for building a MorphLut from a descriptive language - The input patterns is a list of a strings sequences like these: + The input patterns is a list of a strings sequences like these:: - 4:(... - .1. - 111)->1 + 4:(... + .1. + 111)->1 (whitespaces including linebreaks are ignored). The option 4 describes a series of symmetry operations (in this case a 4-rotation), the pattern is described by: - . or X - Ignore - 1 - Pixel is on - 0 - Pixel is off + - . or X - Ignore + - 1 - Pixel is on + - 0 - Pixel is off The result of the operation is described after "->" string. @@ -35,15 +35,16 @@ class LutBuilder: returned if no other match is found. Operations: - 4 - 4 way rotation - N - Negate - 1 - Dummy op for no other operation (an op must always be given) - M - Mirroring + + - 4 - 4 way rotation + - N - Negate + - 1 - Dummy op for no other operation (an op must always be given) + - M - Mirroring - Example: - - lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) - lut = lb.build_lut() + Example:: + + lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) + lut = lb.build_lut() """ def __init__(self, patterns=None, op_name=None): From f0fa458ca8b300d535680ea744e38486bce75269 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 12 Jul 2014 09:57:38 -0700 Subject: [PATCH 341/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 342a99119..3dcc553f4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,8 +4,8 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ -- Fixed install issue if Multiprocessing.Pool is not available - [wiredfool] +- Added docs for ExifTags + [Wintermute3] - More tests for ImageFont, ImageMath, and _util [hugovk] @@ -16,15 +16,20 @@ Changelog (Pillow) - Fix dispose calculations for animated GIFs #765 [larsjsol] -- 32bit mult overflow fix #782 - [wiredfool] - - Added class checking to Image __eq__ function #775 [radarhere, hugovk] - Test PalmImagePlugin and method to skip known bad tests #776 [hugovk, wiredfool] +2.5.1 (2014-07-10) +------------------ + +- Fixed install issue if Multiprocessing.Pool is not available + [wiredfool] + +- 32bit mult overflow fix #782 + [wiredfool] 2.5.0 (2014-07-01) ------------------ From 123fe38ef73e5dc7485e14d1d35a47e93fa680b0 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 15 Jul 2014 00:42:31 +0300 Subject: [PATCH 342/488] Test Image's __ne__ and alpha_composite --- Tests/test_image.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index c1c2c3d86..85b0bba0e 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -102,6 +102,44 @@ class TestImage(PillowTestCase): # Assert self.assertEqual(bbox, (0, 0, 128, 128)) + def test_ne(self): + # Arrange + im1 = Image.new('RGB', (25, 25), 'black') + im2 = Image.new('RGB', (25, 25), 'white') + + # Act / Assert + self.assertTrue(im1 != im2) + + def test_alpha_composite(self): + # http://stackoverflow.com/questions/3374878 + # Arrange + import ImageDraw + + expected_colors = sorted([ + (1122, (128, 127, 0, 255)), + (1089, (0, 255, 0, 255)), + (3300, (255, 0, 0, 255)), + (1156, (170, 85, 0, 192)), + (1122, (0, 255, 0, 128)), + (1122, (255, 0, 0, 128)), + (1089, (0, 255, 0, 0))]) + + dst = Image.new('RGBA', size=(100, 100), color=(0, 255, 0, 255)) + draw = ImageDraw.Draw(dst) + draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128)) + draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0)) + src = Image.new('RGBA', size=(100, 100), color=(255, 0, 0, 255)) + draw = ImageDraw.Draw(src) + draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128)) + draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0)) + + # Act + img = Image.alpha_composite(dst, src) + + # Assert + img_colors = sorted(img.getcolors()) + self.assertEqual(img_colors, expected_colors) + if __name__ == '__main__': unittest.main() From 42032b3286ab7305ad53491ddec19014b37deb0a Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 15 Jul 2014 00:44:12 +0300 Subject: [PATCH 343/488] Fix `ImageStat.mean` docs The docs referred to `ImageStat.pixel`, which isn't a thing at all. In addition, make it absolutely clear that all of the attributes return per-band data. --- docs/reference/ImageStat.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/reference/ImageStat.rst b/docs/reference/ImageStat.rst index c8dfe3062..b8925bf8c 100644 --- a/docs/reference/ImageStat.rst +++ b/docs/reference/ImageStat.rst @@ -22,32 +22,32 @@ for a region of an image. .. py:attribute:: count - Total number of pixels. + Total number of pixels for each band in the image. .. py:attribute:: sum - Sum of all pixels. + Sum of all pixels for each band in the image. .. py:attribute:: sum2 - Squared sum of all pixels. + Squared sum of all pixels for each band in the image. - .. py:attribute:: pixel + .. py:attribute:: mean - Average pixel level. + Average (arithmetic mean) pixel level for each band in the image. .. py:attribute:: median - Median pixel level. + Median pixel level for each band in the image. .. py:attribute:: rms - RMS (root-mean-square). + RMS (root-mean-square) for each band in the image. .. py:attribute:: var - Variance. + Variance for each band in the image. .. py:attribute:: stddev - Standard deviation. + Standard deviation for each band in the image. From 45319bd028c99fdab9fb496340c65a46c8927aba Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 15 Jul 2014 00:48:01 +0300 Subject: [PATCH 344/488] Fix import --- Tests/test_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 85b0bba0e..cd46c9713 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -113,7 +113,7 @@ class TestImage(PillowTestCase): def test_alpha_composite(self): # http://stackoverflow.com/questions/3374878 # Arrange - import ImageDraw + from PIL import ImageDraw expected_colors = sorted([ (1122, (128, 127, 0, 255)), From 8c74cde6f8643edb1daf91a9154dd848b5a55b38 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 14 Jul 2014 17:51:18 -0400 Subject: [PATCH 345/488] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3dcc553f4..95ccd414d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fix `ImageStat` docs + [akx] + - Added docs for ExifTags [Wintermute3] From 529ef12f1fb163992b4c787371aa25acd6b487a3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:02:12 -0700 Subject: [PATCH 346/488] Doc targets + help for makefile --- Makefile | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3dcfe8d13..98e0c647a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,16 @@ +.PHONY: pre clean install test inplace coverage test-dep help docs livedocs +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " clean remove build products" + @echo " install make and install" + @echo " test run tests on installed pillow" + @echo " inplace make inplace extension" + @echo " coverage run coverage test (in progress)" + @echo " docs make html docs" + @echo " docserver run an http server on the docs directory" + @echo " test-dep install coveraget and test dependencies" pre: virtualenv . @@ -18,12 +30,11 @@ clean: rm -r build || true find . -name __pycache__ | xargs rm -r || true - install: python setup.py install python selftest.py --installed -test: install +test: python test-installed.py inplace: clean @@ -42,3 +53,9 @@ coverage: test-dep: pip install coveralls nose nose-cov pep8 pyflakes + +docs: + $(MAKE) -C docs html + +docserver: + cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null& \ No newline at end of file From a0cfa466d96fff20feb204f623770bbd3a049083 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:11:00 -0700 Subject: [PATCH 347/488] Fixing warnings when building docs --- PIL/ImageCms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index fc176952e..c62bffee2 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -637,7 +637,7 @@ def getProfileName(profile): (pyCMS) Gets the internal product name for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, + If profile isn't a valid CmsProfile object or filename to a profile, a PyCMSError is raised If an error occurs while trying to obtain the name tag, a PyCMSError is raised. @@ -876,7 +876,7 @@ def isIntentSupported(profile, intent, direction): input/output/proof profile as you desire. Some profiles are created specifically for one "direction", can cannot - be used for others. Some profiles can only be used for certain + be used for others. Some profiles can only be used for certain rendering intents... so it's best to either verify this before trying to create a transform with them (using this function), or catch the potential PyCMSError that will occur if they don't support the modes From d80eef46ce8e07333d80e10a6f9730b22bdf5728 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:13:01 -0700 Subject: [PATCH 348/488] Fixing warnings when building docs --- docs/PIL.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PIL.rst b/docs/PIL.rst index 3b4706511..07d45810e 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -61,6 +61,7 @@ can be found here. :show-inheritance: .. intentionally skipped documenting this because it's not documented anywhere + :mod:`ImageDraw2` Module ------------------------ @@ -70,6 +71,7 @@ can be found here. :show-inheritance: .. intentionally skipped documenting this because it's deprecated + :mod:`ImageFileIO` Module ------------------------- From a0d6cf01ce275d66fa26de7c8ea7743a18011219 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:24:54 -0700 Subject: [PATCH 349/488] Fixing doc warnings, reformatting docstring comments --- PIL/OleFileIO.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py index 8a3c77be4..e5ecc6e56 100644 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -15,8 +15,11 @@ Improved version of the OleFileIO module from PIL library v1.1.6 See: http://www.pythonware.com/products/pil/index.htm The Python Imaging Library (PIL) is + Copyright (c) 1997-2005 by Secret Labs AB + Copyright (c) 1995-2005 by Fredrik Lundh + OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec See source code and LICENSE.txt for information on usage and redistribution. @@ -1701,10 +1704,12 @@ class OleFileIO: Open a stream as a read-only file object (BytesIO). filename: path of stream in storage tree (except root entry), either: + - a string using Unix path syntax, for example: 'storage_1/storage_1.2/stream' - a list of storage filenames, path to the desired stream/storage. Example: ['storage_1', 'storage_1.2', 'stream'] + return: file object (read-only) raise IOError if filename not found, or if this is not a stream. """ @@ -1722,6 +1727,7 @@ class OleFileIO: filename: path of stream in storage tree. (see openstream for syntax) return: False if object does not exist, its entry type (>0) otherwise: + - STGTY_STREAM: a stream - STGTY_STORAGE: a storage - STGTY_ROOT: the root entry @@ -1812,7 +1818,7 @@ class OleFileIO: filename: path of stream in storage tree (see openstream for syntax) convert_time: bool, if True timestamps will be converted to Python datetime no_conversion: None or list of int, timestamps not to be converted - (for example total editing time is not a real timestamp) + (for example total editing time is not a real timestamp) return: a dictionary of values indexed by id (integer) """ # make sure no_conversion is a list, just to simplify code below: From 5e12c490343457e16109edacd87f3e39020e26c8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:32:14 -0700 Subject: [PATCH 350/488] Fixing doc warning --- PIL/PSDraw.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/PIL/PSDraw.py b/PIL/PSDraw.py index 88593bb9d..26fdb74ea 100644 --- a/PIL/PSDraw.py +++ b/PIL/PSDraw.py @@ -73,9 +73,8 @@ class PSDraw: def setink(self, ink): """ - .. warning:: + .. warning:: This has been in the PIL API for ages but was never implemented. - This has been in the PIL API for ages but was never implemented. """ print("*** NOT YET IMPLEMENTED ***") From 551cdedb45d890a42850a78d2a1d6bae98b5da7c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:57:58 -0700 Subject: [PATCH 351/488] Pulled ImageCms into it's own docpage --- docs/PIL.rst | 8 -------- docs/reference/ImageCms.rst | 13 +++++++++++++ docs/reference/index.rst | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 docs/reference/ImageCms.rst diff --git a/docs/PIL.rst b/docs/PIL.rst index 07d45810e..537b2fd8f 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -52,14 +52,6 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`ImageCms` Module ----------------------- - -.. automodule:: PIL.ImageCms - :members: - :undoc-members: - :show-inheritance: - .. intentionally skipped documenting this because it's not documented anywhere :mod:`ImageDraw2` Module diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst new file mode 100644 index 000000000..2d5bb1388 --- /dev/null +++ b/docs/reference/ImageCms.rst @@ -0,0 +1,13 @@ +.. py:module:: PIL.ImageCms +.. py:currentmodule:: PIL.ImageCms + +:py:mod:`ImageCms` Module +========================= + +The :py:mod:`ImageCms` module provides color profile management +support using the LittleCMS2 color management engine, based on Kevin +Cazabon's PyCMS library. + +.. automodule:: PIL.ImageCms + :members: + :noindex: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 66310e3e7..fca2b387b 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -8,6 +8,7 @@ Reference Image ImageChops ImageColor + ImageCms ImageDraw ImageEnhance ImageFile From 6a928ff6e2bd66cfb14089f9755a272765a42f76 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 21:59:42 -0700 Subject: [PATCH 352/488] Removed leading docstring --- PIL/ImageCms.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index c62bffee2..456bcbb96 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -1,19 +1,18 @@ -""" -The Python Imaging Library. -$Id$ +## The Python Imaging Library. +## $Id$ -Optional color managment support, based on Kevin Cazabon's PyCMS -library. +## Optional color managment support, based on Kevin Cazabon's PyCMS +## library. -History: -2009-03-08 fl Added to PIL. +## History: -Copyright (C) 2002-2003 Kevin Cazabon -Copyright (c) 2009 by Fredrik Lundh +## 2009-03-08 fl Added to PIL. -See the README file for information on usage and redistribution. See -below for the original description. -""" +## Copyright (C) 2002-2003 Kevin Cazabon +## Copyright (c) 2009 by Fredrik Lundh + +## See the README file for information on usage and redistribution. See +## below for the original description. from __future__ import print_function From 5eef39f3fb93635d4aaf55d593d0bb683d21366e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 22:00:29 -0700 Subject: [PATCH 353/488] Asserting copyright over lcms2 port --- PIL/ImageCms.py | 1 + _imagingcms.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 456bcbb96..4ea6409d6 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -10,6 +10,7 @@ ## Copyright (C) 2002-2003 Kevin Cazabon ## Copyright (c) 2009 by Fredrik Lundh +## Copyright (c) 2013 by Eric Soroos ## See the README file for information on usage and redistribution. See ## below for the original description. diff --git a/_imagingcms.c b/_imagingcms.c index df26e1a2d..1b7ef49e1 100644 --- a/_imagingcms.c +++ b/_imagingcms.c @@ -6,6 +6,8 @@ * http://www.cazabon.com * Adapted/reworked for PIL by Fredrik Lundh * Copyright (c) 2009 Fredrik Lundh + * Updated to LCMS2 + * Copyright (c) 2013 Eric Soroos * * pyCMS home page: http://www.cazabon.com/pyCMS * littleCMS home page: http://www.littlecms.com From 4a5d73cb1e1dbc0e781d03d24e62f31d73f14291 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 14 Jul 2014 22:25:02 -0700 Subject: [PATCH 354/488] Fixing errors when compiling docs --- docs/reference/Image.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index bf64c835d..11666dd0b 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -49,7 +49,10 @@ Functions .. autofunction:: open - .. warning:: > To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr. + .. warning:: To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also `the logging documentation`_ to have warnings output to the logging facility instead of stderr. + + .. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb + .. _the logging documentation: https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module Image processing ^^^^^^^^^^^^^^^^ From 11ac1e34cf1f5bdbe17951f102e25fc283723570 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 15 Jul 2014 04:18:39 -0400 Subject: [PATCH 355/488] Update [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 95ccd414d..6aaf8536c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Doc cleanup + [wiredfool] + - Fix `ImageStat` docs [akx] From 6c9940e9d1f3051a93d896d8efe7911442c600fe Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 15 Jul 2014 12:23:02 +0300 Subject: [PATCH 356/488] More tests for SpiderImagePlugin.py --- Tests/test_file_spider.py | 49 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 622bfd624..65e4d2782 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -3,13 +3,13 @@ from helper import unittest, PillowTestCase, lena from PIL import Image from PIL import SpiderImagePlugin -test_file = "Tests/images/lena.spider" +TEST_FILE = "Tests/images/lena.spider" class TestImageSpider(PillowTestCase): def test_sanity(self): - im = Image.open(test_file) + im = Image.open(TEST_FILE) im.load() self.assertEqual(im.mode, "F") self.assertEqual(im.size, (128, 128)) @@ -30,7 +30,50 @@ class TestImageSpider(PillowTestCase): self.assertEqual(im2.format, "SPIDER") def test_isSpiderImage(self): - self.assertTrue(SpiderImagePlugin.isSpiderImage(test_file)) + self.assertTrue(SpiderImagePlugin.isSpiderImage(TEST_FILE)) + + def test_tell(self): + # Arrange + im = Image.open(TEST_FILE) + + # Act + index = im.tell() + + # Assert + self.assertEqual(index, 0) + + def test_loadImageSeries(self): + # Arrange + not_spider_file = "Tests/images/lena.ppm" + file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"] + + # Act + img_list = SpiderImagePlugin.loadImageSeries(file_list) + + # Assert + self.assertEqual(len(img_list), 1) + self.assertIsInstance(img_list[0], Image.Image) + self.assertEqual(img_list[0].size, (128, 128)) + + def test_loadImageSeries_no_input(self): + # Arrange + file_list = None + + # Act + img_list = SpiderImagePlugin.loadImageSeries(file_list) + + # Assert + self.assertEqual(img_list, None) + + def test_isInt_not_a_number(self): + # Arrange + not_a_number = "a" + + # Act + ret = SpiderImagePlugin.isInt(not_a_number) + + # Assert + self.assertEqual(ret, 0) if __name__ == '__main__': From 94ac5319606565f53f6a0fcadd3345cb918af6bd Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 15 Jul 2014 06:01:37 -0400 Subject: [PATCH 357/488] Update [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6aaf8536c..21166f1b4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- More tests for SpiderImagePlugin.py + [hugovk] + - Doc cleanup [wiredfool] From 4fe5d520fbd7668df0c5998753be3ed158af32b0 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 15 Jul 2014 06:02:34 -0400 Subject: [PATCH 358/488] Bump Though I hate the 'dev' designation I want something to indicate master is where development for the next major version happens. I think we've previously disagreed on simply 'X.X.X' so I'm going with 'X.X.Xdev' to see if that is more palatable. :-) --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index d446aa19b..8702d24cd 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.5.0' # Pillow +PILLOW_VERSION = '2.6.0dev' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 92258032f..de2603cbc 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.5.0" +#define PILLOW_VERSION "2.6.0dev" #include "Python.h" diff --git a/setup.py b/setup.py index e94e34d28..5017b56e9 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.5.0' +PILLOW_VERSION = '2.6.0dev' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From 4b40839970b76e00921b0beef3a1403fde229a55 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 15 Jul 2014 06:15:31 -0400 Subject: [PATCH 359/488] Revert "Bump" This reverts commit 4fe5d520fbd7668df0c5998753be3ed158af32b0. Hah, foiled by PEP8 --- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 8702d24cd..d446aa19b 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.6.0dev' # Pillow +PILLOW_VERSION = '2.5.0' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index de2603cbc..92258032f 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.6.0dev" +#define PILLOW_VERSION "2.5.0" #include "Python.h" diff --git a/setup.py b/setup.py index 5017b56e9..e94e34d28 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.6.0dev' +PILLOW_VERSION = '2.5.0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From 0dddff63d93961e4ad85b03b68992fe03c33acd6 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Tue, 15 Jul 2014 10:21:17 -0400 Subject: [PATCH 360/488] Added two sample MPO image files for testing. Both MPO files represent 3D images and thus have a left image and a right image. Both were taken with a Nintendo 3DS in Breakheart Reservation in Saugus, MA. --- Images/frozenpond.mpo | Bin 0 -> 166209 bytes Images/sugarshack.mpo | Bin 0 -> 120198 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Images/frozenpond.mpo create mode 100644 Images/sugarshack.mpo diff --git a/Images/frozenpond.mpo b/Images/frozenpond.mpo new file mode 100644 index 0000000000000000000000000000000000000000..2dfe44ac1212b6868a40d851a6c33a39895eab25 GIT binary patch literal 166209 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�h=*25=Vs(}6D+*1^WQb$otX;}o5=17J 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

e)Wjx6s zC(@-xQdccmv!tI(xLZ&m;|Hkpt2$&iQQJIBhZ*IKPc5|)ce*q6D+r@zxhO_G>&-Pa zH5muTXfyF!9#=u2}UpGLN_DC<7pNU!eGp?*}^!uppEMhXNE+vc;fJbmUR|~CN&NUME5VEX-Wk|!ZOfi8d8u^mGUr*kh7CT} zNhW>>q^Zg->~ME?Lp7ZPQPN+`yMcsA!5N1@YQ!Q*F>9OD_SB^HXOQ<*bw z#XcgozG)}7j!5n%RSK*)z&&dU7^2q%afX^s4?>Z0Ophz zyj4eSj=$lKpqg%?X1tFmQ(~4Jb>_WJ<63LAg6~hx_NFV zhj*M#;!jH6@Rq0jlO3g;n|ztqhxhlZmF1~1ae5=T(+UfJE<86Zay#=$sWU{PAgb3> ze6gth5$bCBu443M>uUwlZJ=1b;-B6zt}0*1=G2t(04%HA`WjKu*$P)~=(T5c9JkJ1KoT@iqtN4;l4+HELZurXWFwq) zH4WL=a=f=4uXutB9X-X;Z8*Td>U}Gp@YbIUw?Er@p2pNJ#^eNX8uk>@v(y^2ZjQ51 zygT7UL=rmSalj^+G^-yWk>DO=gDod~({EC5rlus*n?p1UF-(2cJml1RrRWV}1T500 zJ4bWV`qIB*)`N8zF5dDPByTmK#DOuwjPv?d=bSHY_uHI^>g=Bcv;zsc{n{lVccerSaH{EUrAI`6p$q^T3LbJ;?)Hc@_FR>&Wk-91L ztv?ei7PcCcumZ0PV;fJcW$0~kzl4YOSBIjzw2`+m=HdqKPdWW+&xmIGcY^MtjNs#T z=I`{a8tBCv-&4m@d7(hkfIu8zSEqPdN4onisi?9xL%y-1v@NO%XF16-ekZ$Gl{ zE+#ArNSHZ4tz@L*E@Mh}w<|U@4baVVJmFSLjJi4RTQ}l0(}YlgBTK`}+O&_nwcOrv zQF<3Nn;SM;b(RG(k(`dXqfyc&xVxHp4%ng_MtTEE%+KO?GHzzLwXuohZ#XmH0ywL1 zD%_PbM#|-L><7}HE49R~v^m`a5bC;%%L$V+VHkf871Y~Z{_0ssoZ#V4TvBmzK_{(> zV^Y5_xvp58@DB$SYH6;SR%oPUlz(vNch-c4-zqHGUtBZpy^N^oypEN#1Tj6E$b>Vo z-Jfc-tYqEvJr-w<+7L(#dUI8vg(g09;4uCb>?^IVq~7x7W6&$e{!)b}qk2|uqoZc+ zFRVxxDfg80lj};|$$mt0T_X=J^#pFdqm0#w-3&4Qn)e-2qRFL@*$ZMhqyKy$as;rLXRwdL zg33rj%IK_{R~Twd*`~tJRF5-8c8xdSkhrNWZexYZ2ZA1th*ES--iCCGuIk*@)sW zdRA1Ub+mU^vU!uNiWNcO254`oF?P_tvxymHWp6Ex5PH^>7bwmnD#-n_-ja9K1BzWo zGX{{PfE6PhE6siuY_rw;WQw3e74OOiLDsF^qGR0YwL6=sXR$GjkvzLtW2)fuTK4ey znt^1(q)W68+|r1e)Sl+bNs}XQ9Q4Ii7FQ9W*u%^mk9w{27}<3wt?f{%?=fji`Lk=~-$W4)!JH-YbC{{Vyr-fMzG+ABoh_9{rlV_sRct);d5 z$mV;=fe{0YgGxR{DXwDtwvlOT4DQHWpWY!k6wOajvp!YKW!d5x!1Sq;WvfYD84WV) zmoIZJ-?2r?fzg=OvP98Hk}M>MVjL01aaS9?%w@fqdq#-K7u#+LGfADHx+wOo1|*#B z$>~Mz8*XE2@DDASwhe?gnb7fAu**4^+(-*T$7E&4-3FG8g4F3Pq*oKl;AJDh_pHAU zY6)c}+!p{w&J3~V_=Oj+pT%Mw0^KzoLh}CMOGsO6V2%JC=$$M z=%Xw-_pEE%V`*&}kOpTNAl6)}_cVe}W2V*Is@tvrK3K@~t?QeFO&2Y0fU58ZKx+OQ zvne~hjc*Ev@>YK-cM;W(@~byj(9LHwa)u1dOA%Uh+-mmJ=6pWi+7|Z@JZfRJkfH

y;wtyCVGj&f?n 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 C`?!z* literal 0 HcmV?d00001 diff --git a/Images/sugarshack.mpo b/Images/sugarshack.mpo new file mode 100644 index 0000000000000000000000000000000000000000..85346fcc996d8dfee3af746814eb694f908d4c10 GIT binary patch literal 120198 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 y`1P$7zJ)4hg6^ct`UVN>+!;T-M& literal 0 HcmV?d00001 From 17223001df8d849a92cd552ba9b3f4fa700c5a3d Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 15 Jul 2014 18:38:10 +0300 Subject: [PATCH 361/488] flake8 --- PIL/ImagePalette.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 59886827a..ee3c22544 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -23,13 +23,13 @@ from PIL import Image, ImageColor class ImagePalette: "Color palette for palette mapped images" - def __init__(self, mode = "RGB", palette = None, size = 0): + def __init__(self, mode="RGB", palette=None, size=0): self.mode = mode - self.rawmode = None # if set, palette contains raw data + self.rawmode = None # if set, palette contains raw data self.palette = palette or list(range(256))*len(self.mode) self.colors = {} self.dirty = None - if ((size == 0 and len(self.mode)*256 != len(self.palette)) or + if ((size == 0 and len(self.mode)*256 != len(self.palette)) or (size != 0 and size != len(self.palette))): raise ValueError("wrong palette size") @@ -55,7 +55,7 @@ class ImagePalette: return self.palette arr = array.array("B", self.palette) if hasattr(arr, 'tobytes'): - #py3k has a tobytes, tostring is deprecated. + # py3k has a tobytes, tostring is deprecated. return arr.tobytes() return arr.tostring() @@ -109,6 +109,7 @@ class ImagePalette: fp.write("\n") fp.close() + # -------------------------------------------------------------------- # Internal @@ -119,6 +120,7 @@ def raw(rawmode, data): palette.dirty = 1 return palette + # -------------------------------------------------------------------- # Factories @@ -128,23 +130,27 @@ def _make_linear_lut(black, white): for i in range(256): lut.append(white*i//255) else: - raise NotImplementedError # FIXME + raise NotImplementedError # FIXME return lut + def _make_gamma_lut(exp, mode="RGB"): lut = [] for i in range(256): lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5)) return lut + def new(mode, data): return Image.core.new_palette(mode, data) + def negative(mode="RGB"): palette = list(range(256)) palette.reverse() return ImagePalette(mode, palette * len(mode)) + def random(mode="RGB"): from random import randint palette = [] @@ -152,6 +158,7 @@ def random(mode="RGB"): palette.append(randint(0, 255)) return ImagePalette(mode, palette) + def sepia(white="#fff0c0"): r, g, b = ImageColor.getrgb(white) r = _make_linear_lut(0, r) @@ -159,9 +166,11 @@ def sepia(white="#fff0c0"): b = _make_linear_lut(0, b) return ImagePalette("RGB", r + g + b) + def wedge(mode="RGB"): return ImagePalette(mode, list(range(256)) * len(mode)) + def load(filename): # FIXME: supports GIMP gradients only @@ -177,8 +186,8 @@ def load(filename): p = GimpPaletteFile.GimpPaletteFile(fp) lut = p.getpalette() except (SyntaxError, ValueError): - #import traceback - #traceback.print_exc() + # import traceback + # traceback.print_exc() pass if not lut: @@ -188,8 +197,8 @@ def load(filename): p = GimpGradientFile.GimpGradientFile(fp) lut = p.getpalette() except (SyntaxError, ValueError): - #import traceback - #traceback.print_exc() + # import traceback + # traceback.print_exc() pass if not lut: @@ -206,4 +215,4 @@ def load(filename): if not lut: raise IOError("cannot load palette") - return lut # data, rawmode + return lut # data, rawmode From 2609a24221e3bbb2993d8fb28e86d3ba4daffcac Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Tue, 15 Jul 2014 17:43:31 -0400 Subject: [PATCH 362/488] Moved images to correct place. Accidentally uploaded images to their initial test location, not the proper location used by other tests. --- {Images => Tests/images}/frozenpond.mpo | Bin {Images => Tests/images}/sugarshack.mpo | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename {Images => Tests/images}/frozenpond.mpo (100%) rename {Images => Tests/images}/sugarshack.mpo (100%) diff --git a/Images/frozenpond.mpo b/Tests/images/frozenpond.mpo similarity index 100% rename from Images/frozenpond.mpo rename to Tests/images/frozenpond.mpo diff --git a/Images/sugarshack.mpo b/Tests/images/sugarshack.mpo similarity index 100% rename from Images/sugarshack.mpo rename to Tests/images/sugarshack.mpo From 7dfec434fa9b89c0788e9704dad99652f71cfd31 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 15 Jul 2014 16:20:20 -0700 Subject: [PATCH 363/488] Pulled ImageMorph into its own page --- docs/PIL.rst | 7 ------- docs/reference/ImageMorph.rst | 13 +++++++++++++ docs/reference/index.rst | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 docs/reference/ImageMorph.rst diff --git a/docs/PIL.rst b/docs/PIL.rst index 537b2fd8f..5b429dc4b 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -72,13 +72,6 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`ImageMorph` Module ------------------------- - -.. automodule:: PIL.ImageMorph - :members: - :undoc-members: - :show-inheritance: :mod:`ImageShow` Module ----------------------- diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst new file mode 100644 index 000000000..eaf3b1c5e --- /dev/null +++ b/docs/reference/ImageMorph.rst @@ -0,0 +1,13 @@ +.. py:module:: PIL.ImageMorph +.. py:currentmodule:: PIL.ImageMorph + +:py:mod:`ImageMorph` Module +=========================== + +The :py:mod:`ImageMorph` module provides morphology operations on images. + +.. automodule:: PIL.ImageMorph + :members: + :undoc-members: + :show-inheritance: + :noindex: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index fca2b387b..d6baf216e 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -16,6 +16,7 @@ Reference ImageFont ImageGrab ImageMath + ImageMorph ImageOps ImagePalette ImagePath From b6c33596b3bd4058877b4ca9eb7bdc46a20da5e6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 15 Jul 2014 16:56:59 -0700 Subject: [PATCH 364/488] Broke out OleFileIO into its own page, Added docs from the readme --- PIL/OleFileIO.py | 34 ++-- docs/PIL.rst | 8 - docs/reference/OleFileIO.rst | 364 +++++++++++++++++++++++++++++++++++ docs/reference/index.rst | 1 + 4 files changed, 381 insertions(+), 26 deletions(-) create mode 100644 docs/reference/OleFileIO.rst diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py index e5ecc6e56..57c9c349e 100644 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -1,31 +1,29 @@ #!/usr/local/bin/python # -*- coding: latin-1 -*- -""" -OleFileIO_PL: -Module to read Microsoft OLE2 files (also called Structured Storage or -Microsoft Compound Document File Format), such as Microsoft Office -documents, Image Composer and FlashPix files, Outlook messages, ... -This version is compatible with Python 2.6+ and 3.x +## OleFileIO_PL: +## Module to read Microsoft OLE2 files (also called Structured Storage or +## Microsoft Compound Document File Format), such as Microsoft Office +## documents, Image Composer and FlashPix files, Outlook messages, ... +## This version is compatible with Python 2.6+ and 3.x -version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info +## version 0.30 2014-02-04 Philippe Lagadec - http://www.decalage.info -Project website: http://www.decalage.info/python/olefileio +## Project website: http://www.decalage.info/python/olefileio -Improved version of the OleFileIO module from PIL library v1.1.6 -See: http://www.pythonware.com/products/pil/index.htm +## Improved version of the OleFileIO module from PIL library v1.1.6 +## See: http://www.pythonware.com/products/pil/index.htm -The Python Imaging Library (PIL) is +## The Python Imaging Library (PIL) is - Copyright (c) 1997-2005 by Secret Labs AB - - Copyright (c) 1995-2005 by Fredrik Lundh +## Copyright (c) 1997-2005 by Secret Labs AB +## Copyright (c) 1995-2005 by Fredrik Lundh -OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec +## OleFileIO_PL changes are Copyright (c) 2005-2014 by Philippe Lagadec -See source code and LICENSE.txt for information on usage and redistribution. +## See source code and LICENSE.txt for information on usage and redistribution. + +## WARNING: THIS IS (STILL) WORK IN PROGRESS. -WARNING: THIS IS (STILL) WORK IN PROGRESS. -""" # Starting with OleFileIO_PL v0.30, only Python 2.6+ and 3.x is supported # This import enables print() as a function rather than a keyword diff --git a/docs/PIL.rst b/docs/PIL.rst index 5b429dc4b..8bf89c685 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -97,14 +97,6 @@ can be found here. :undoc-members: :show-inheritance: -:mod:`OleFileIO` Module ------------------------ - -.. automodule:: PIL.OleFileIO - :members: - :undoc-members: - :show-inheritance: - :mod:`PaletteFile` Module ------------------------- diff --git a/docs/reference/OleFileIO.rst b/docs/reference/OleFileIO.rst new file mode 100644 index 000000000..74c4b7b36 --- /dev/null +++ b/docs/reference/OleFileIO.rst @@ -0,0 +1,364 @@ +.. py:module:: PIL.OleFileIO +.. py:currentmodule:: PIL.OleFileIO + +:py:mod:`OleFileIO` Module +=========================== + +The :py:mod:`OleFileIO` module reads Microsoft OLE2 files (also called +Structured Storage or Microsoft Compound Document File Format), such +as Microsoft Office documents, Image Composer and FlashPix files, and +Outlook messages. + +This module is the `OleFileIO\_PL`_ project by Philippe Lagadec, v0.30, +merged back into Pillow. + +.. _OleFileIO\_PL: http://www.decalage.info/python/olefileio + +How to use this module +---------------------- + +For more information, see also the file **PIL/OleFileIO.py**, sample +code at the end of the module itself, and docstrings within the code. + +About the structure of OLE files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An OLE file can be seen as a mini file system or a Zip archive: It +contains **streams** of data that look like files embedded within the +OLE file. Each stream has a name. For example, the main stream of a MS +Word document containing its text is named "WordDocument". + +An OLE file can also contain **storages**. A storage is a folder that +contains streams or other storages. For example, a MS Word document with +VBA macros has a storage called "Macros". + +Special streams can contain **properties**. A property is a specific +value that can be used to store information such as the metadata of a +document (title, author, creation date, etc). Property stream names +usually start with the character '05'. + +For example, a typical MS Word document may look like this: + +:: + + \x05DocumentSummaryInformation (stream) + \x05SummaryInformation (stream) + WordDocument (stream) + Macros (storage) + PROJECT (stream) + PROJECTwm (stream) + VBA (storage) + Module1 (stream) + ThisDocument (stream) + _VBA_PROJECT (stream) + dir (stream) + ObjectPool (storage) + +Test if a file is an OLE container +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use isOleFile to check if the first bytes of the file contain the Magic +for OLE files, before opening it. isOleFile returns True if it is an OLE +file, False otherwise. + +.. code-block:: python + + assert OleFileIO.isOleFile('myfile.doc') + +Open an OLE file from disk +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create an OleFileIO object with the file path as parameter: + +.. code-block:: python + + ole = OleFileIO.OleFileIO('myfile.doc') + +Open an OLE file from a file-like object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is useful if the file is not on disk, e.g. already stored in a +string or as a file-like object. + +.. code-block:: python + + ole = OleFileIO.OleFileIO(f) + +For example the code below reads a file into a string, then uses BytesIO +to turn it into a file-like object. + +.. code-block:: python + + data = open('myfile.doc', 'rb').read() + f = io.BytesIO(data) # or StringIO.StringIO for Python 2.x + ole = OleFileIO.OleFileIO(f) + +How to handle malformed OLE files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, the parser is configured to be as robust and permissive as +possible, allowing to parse most malformed OLE files. Only fatal errors +will raise an exception. It is possible to tell the parser to be more +strict in order to raise exceptions for files that do not fully conform +to the OLE specifications, using the raise\_defect option: + +.. code-block:: python + + ole = OleFileIO.OleFileIO('myfile.doc', raise_defects=DEFECT_INCORRECT) + +When the parsing is done, the list of non-fatal issues detected is +available as a list in the parsing\_issues attribute of the OleFileIO +object: + +.. code-block:: python + + print('Non-fatal issues raised during parsing:') + if ole.parsing_issues: + for exctype, msg in ole.parsing_issues: + print('- %s: %s' % (exctype.__name__, msg)) + else: + print('None') + +Syntax for stream and storage path +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Two different syntaxes are allowed for methods that need or return the +path of streams and storages: + +1) Either a **list of strings** including all the storages from the root + up to the stream/storage name. For example a stream called + "WordDocument" at the root will have ['WordDocument'] as full path. A + stream called "ThisDocument" located in the storage "Macros/VBA" will + be ['Macros', 'VBA', 'ThisDocument']. This is the original syntax + from PIL. While hard to read and not very convenient, this syntax + works in all cases. + +2) Or a **single string with slashes** to separate storage and stream + names (similar to the Unix path syntax). The previous examples would + be 'WordDocument' and 'Macros/VBA/ThisDocument'. This syntax is + easier, but may fail if a stream or storage name contains a slash. + +Both are case-insensitive. + +Switching between the two is easy: + +.. code-block:: python + + slash_path = '/'.join(list_path) + list_path = slash_path.split('/') + +Get the list of streams +~~~~~~~~~~~~~~~~~~~~~~~ + +listdir() returns a list of all the streams contained in the OLE file, +including those stored in storages. Each stream is listed itself as a +list, as described above. + +.. code-block:: python + + print(ole.listdir()) + +Sample result: + +.. code-block:: python + + [['\x01CompObj'], ['\x05DocumentSummaryInformation'], ['\x05SummaryInformation'] + , ['1Table'], ['Macros', 'PROJECT'], ['Macros', 'PROJECTwm'], ['Macros', 'VBA', + 'Module1'], ['Macros', 'VBA', 'ThisDocument'], ['Macros', 'VBA', '_VBA_PROJECT'] + , ['Macros', 'VBA', 'dir'], ['ObjectPool'], ['WordDocument']] + +As an option it is possible to choose if storages should also be listed, +with or without streams: + +.. code-block:: python + + ole.listdir (streams=False, storages=True) + +Test if known streams/storages exist: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +exists(path) checks if a given stream or storage exists in the OLE file. + +.. code-block:: python + + if ole.exists('worddocument'): + print("This is a Word document.") + if ole.exists('macros/vba'): + print("This document seems to contain VBA macros.") + +Read data from a stream +~~~~~~~~~~~~~~~~~~~~~~~ + +openstream(path) opens a stream as a file-like object. + +The following example extracts the "Pictures" stream from a PPT file: + +.. code-block:: python + + pics = ole.openstream('Pictures') + data = pics.read() + + +Get information about a stream/storage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Several methods can provide the size, type and timestamps of a given +stream/storage: + +get\_size(path) returns the size of a stream in bytes: + +.. code-block:: python + + s = ole.get_size('WordDocument') + +get\_type(path) returns the type of a stream/storage, as one of the +following constants: STGTY\_STREAM for a stream, STGTY\_STORAGE for a +storage, STGTY\_ROOT for the root entry, and False for a non existing +path. + +.. code-block:: python + + t = ole.get_type('WordDocument') + +get\_ctime(path) and get\_mtime(path) return the creation and +modification timestamps of a stream/storage, as a Python datetime object +with UTC timezone. Please note that these timestamps are only present if +the application that created the OLE file explicitly stored them, which +is rarely the case. When not present, these methods return None. + +.. code-block:: python + + c = ole.get_ctime('WordDocument') + m = ole.get_mtime('WordDocument') + +The root storage is a special case: You can get its creation and +modification timestamps using the OleFileIO.root attribute: + +.. code-block:: python + + c = ole.root.getctime() + m = ole.root.getmtime() + +Extract metadata +~~~~~~~~~~~~~~~~ + +get\_metadata() will check if standard property streams exist, parse all +the properties they contain, and return an OleMetadata object with the +found properties as attributes. + +.. code-block:: python + + meta = ole.get_metadata() + print('Author:', meta.author) + print('Title:', meta.title) + print('Creation date:', meta.create_time) + # print all metadata: + meta.dump() + +Available attributes include: + +:: + + codepage, title, subject, author, keywords, comments, template, + last_saved_by, revision_number, total_edit_time, last_printed, create_time, + last_saved_time, num_pages, num_words, num_chars, thumbnail, + creating_application, security, codepage_doc, category, presentation_target, + bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips, + scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty, + chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed, + version, dig_sig, content_type, content_status, language, doc_version + +See the source code of the OleMetadata class for more information. + +Parse a property stream +~~~~~~~~~~~~~~~~~~~~~~~ + +get\_properties(path) can be used to parse any property stream that is +not handled by get\_metadata. It returns a dictionary indexed by +integers. Each integer is the index of the property, pointing to its +value. For example in the standard property stream +'05SummaryInformation', the document title is property #2, and the +subject is #3. + +.. code-block:: python + + p = ole.getproperties('specialprops') + +By default as in the original PIL version, timestamp properties are +converted into a number of seconds since Jan 1,1601. With the option +convert\_time, you can obtain more convenient Python datetime objects +(UTC timezone). If some time properties should not be converted (such as +total editing time in '05SummaryInformation'), the list of indexes can +be passed as no\_conversion: + +.. code-block:: python + + p = ole.getproperties('specialprops', convert_time=True, no_conversion=[10]) + +Close the OLE file +~~~~~~~~~~~~~~~~~~ + +Unless your application is a simple script that terminates after +processing an OLE file, do not forget to close each OleFileIO object +after parsing to close the file on disk. + +.. code-block:: python + + ole.close() + +Use OleFileIO as a script +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +OleFileIO can also be used as a script from the command-line to +display the structure of an OLE file and its metadata, for example: + +:: + + PIL/OleFileIO.py myfile.doc + +You can use the option -c to check that all streams can be read fully, +and -d to generate very verbose debugging information. + +How to contribute +----------------- + +The code is available in `a Mercurial repository on +bitbucket `_. You may use +it to submit enhancements or to report any issue. + +If you would like to help us improve this module, or simply provide +feedback, please `contact me `_. You can +help in many ways: + +- test this module on different platforms / Python versions +- find and report bugs +- improve documentation, code samples, docstrings +- write unittest test cases +- provide tricky malformed files + +How to report bugs +------------------ + +To report a bug, for example a normal file which is not parsed +correctly, please use the `issue reporting +page `_, +or if you prefer to do it privately, use this `contact +form `_. Please provide all the +information about the context and how to reproduce the bug. + +If possible please join the debugging output of OleFileIO. For this, +launch the following command : + +:: + + PIL/OleFileIO.py -d -c file >debug.txt + + +Classes and Methods +------------------- + +.. automodule:: PIL.OleFileIO + :members: + :undoc-members: + :show-inheritance: + :noindex: diff --git a/docs/reference/index.rst b/docs/reference/index.rst index d6baf216e..2f10b861d 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -26,5 +26,6 @@ Reference ImageTk ImageWin ExifTags + OleFileIO PSDraw ../PIL From 09b0d1cfa6ce30fea5ce8adbfea009085e955c7e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 15 Jul 2014 21:24:52 -0700 Subject: [PATCH 365/488] converted to current docutils format --- PIL/OleFileIO.py | 159 +++++++++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 75 deletions(-) diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py index 57c9c349e..e35bfa679 100644 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -371,8 +371,9 @@ for key in list(vars().keys()): def isOleFile (filename): """ Test if file is an OLE container (according to its header). - filename: file name or path (str, unicode) - return: True if OLE, False otherwise. + + :param filename: file name or path (str, unicode) + :returns: True if OLE, False otherwise. """ f = open(filename, 'rb') header = f.read(len(MAGIC)) @@ -398,8 +399,8 @@ def i16(c, o = 0): """ Converts a 2-bytes (16 bits) string to an integer. - c: string containing bytes to convert - o: offset of bytes to convert in string + :param c: string containing bytes to convert + :param o: offset of bytes to convert in string """ return i8(c[o]) | (i8(c[o+1])<<8) @@ -408,8 +409,8 @@ def i32(c, o = 0): """ Converts a 4-bytes (32 bits) string to an integer. - c: string containing bytes to convert - o: offset of bytes to convert in string + :param c: string containing bytes to convert + :param o: offset of bytes to convert in string """ ## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)) ## # [PL]: added int() because "<<" gives long int since Python 2.4 @@ -420,7 +421,8 @@ def i32(c, o = 0): def _clsid(clsid): """ Converts a CLSID to a human-readable string. - clsid: string of length 16. + + :param clsid: string of length 16. """ assert len(clsid) == 16 # if clsid is only made of null bytes, return an empty string: @@ -440,8 +442,8 @@ def _unicode(s, errors='replace'): """ Map unicode string to Latin 1. (Python with Unicode support) - s: UTF-16LE unicode string to convert to Latin-1 - errors: 'replace', 'ignore' or 'strict'. + :param s: UTF-16LE unicode string to convert to Latin-1 + :param errors: 'replace', 'ignore' or 'strict'. """ #TODO: test if it OleFileIO works with Unicode strings, instead of # converting to Latin-1. @@ -651,14 +653,14 @@ class _OleStream(io.BytesIO): """ Constructor for _OleStream class. - fp : file object, the OLE container or the MiniFAT stream - sect : sector index of first sector in the stream - size : total size of the stream - offset : offset in bytes for the first FAT or MiniFAT sector - sectorsize: size of one sector - fat : array/list of sector indexes (FAT or MiniFAT) - filesize : size of OLE file (for debugging) - return : a BytesIO instance containing the OLE stream + :param fp : file object, the OLE container or the MiniFAT stream + :param sect : sector index of first sector in the stream + :param size : total size of the stream + :param offset : offset in bytes for the first FAT or MiniFAT sector + :param sectorsize: size of one sector + :param fat : array/list of sector indexes (FAT or MiniFAT) + :param filesize : size of OLE file (for debugging) + :returns : a BytesIO instance containing the OLE stream """ debug('_OleStream.__init__:') debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s' @@ -794,9 +796,9 @@ class _OleDirectoryEntry: Constructor for an _OleDirectoryEntry object. Parses a 128-bytes entry from the OLE Directory stream. - entry : string (must be 128 bytes long) - sid : index of this directory entry in the OLE file directory - olefile: OleFileIO containing this directory entry + :param entry : string (must be 128 bytes long) + :param sid : index of this directory entry in the OLE file directory + :param olefile: OleFileIO containing this directory entry """ self.sid = sid # ref to olefile is stored for future use @@ -990,7 +992,7 @@ class _OleDirectoryEntry: """ Return modification time of a directory entry. - return: None if modification time is null, a python datetime object + :returns: None if modification time is null, a python datetime object otherwise (UTC timezone) new in version 0.26 @@ -1004,7 +1006,7 @@ class _OleDirectoryEntry: """ Return creation time of a directory entry. - return: None if modification time is null, a python datetime object + :returns: None if modification time is null, a python datetime object otherwise (UTC timezone) new in version 0.26 @@ -1021,7 +1023,8 @@ class OleFileIO: OLE container object This class encapsulates the interface to an OLE 2 structured - storage file. Use the {@link listdir} and {@link openstream} methods to + storage file. Use the :py:meth:`~PIL.OleFileIO.OleFileIO.listdir` and + :py:meth:`~PIL.OleFileIO.OleFileIO.openstream` methods to access the contents of this file. Object names are given as a list of strings, one for each subentry @@ -1049,8 +1052,8 @@ class OleFileIO: """ Constructor for OleFileIO class. - filename: file to open. - raise_defects: minimal level for defects to be raised as exceptions. + :param filename: file to open. + :param raise_defects: minimal level for defects to be raised as exceptions. (use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a security-oriented application, see source code for details) """ @@ -1069,13 +1072,13 @@ class OleFileIO: It may raise an IOError exception according to the minimal level chosen for the OleFileIO object. - defect_level: defect level, possible values are: + :param defect_level: defect level, possible values are: DEFECT_UNSURE : a case which looks weird, but not sure it's a defect DEFECT_POTENTIAL : a potential defect DEFECT_INCORRECT : an error according to specifications, but parsing can go on DEFECT_FATAL : an error which cannot be ignored, parsing is impossible - message: string describing the defect, used with raised exception. - exception_type: exception class to be raised, IOError by default + :param message: string describing the defect, used with raised exception. + :param exception_type: exception class to be raised, IOError by default """ # added by [PL] if defect_level >= self._raise_defects_level: @@ -1090,7 +1093,7 @@ class OleFileIO: Open an OLE2 file. Reads the header, FAT and directory. - filename: string-like or file-like object + :param filename: string-like or file-like object """ #[PL] check if filename is a string-like or file-like object: # (it is better to check for a read() method) @@ -1277,8 +1280,8 @@ class OleFileIO: Checks if a stream has not been already referenced elsewhere. This method should only be called once for each known stream, and only if stream size is not null. - first_sect: index of first sector of the stream in FAT - minifat: if True, stream is located in the MiniFAT, else in the FAT + :param first_sect: index of first sector of the stream in FAT + :param minifat: if True, stream is located in the MiniFAT, else in the FAT """ if minifat: debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect) @@ -1372,8 +1375,9 @@ class OleFileIO: def loadfat_sect(self, sect): """ Adds the indexes of the given sector to the FAT - sect: string containing the first FAT sector, or array of long integers - return: index of last FAT sector. + + :param sect: string containing the first FAT sector, or array of long integers + :returns: index of last FAT sector. """ # a FAT sector is an array of ulong integers. if isinstance(sect, array.array): @@ -1506,8 +1510,9 @@ class OleFileIO: def getsect(self, sect): """ Read given sector from file on disk. - sect: sector index - returns a string containing the sector data. + + :param sect: sector index + :returns: a string containing the sector data. """ # [PL] this original code was wrong when sectors are 4KB instead of # 512 bytes: @@ -1531,7 +1536,8 @@ class OleFileIO: def loaddirectory(self, sect): """ Load the directory. - sect: sector index of directory stream. + + :param sect: sector index of directory stream. """ # The directory is stored in a standard # substream, independent of its size. @@ -1568,9 +1574,10 @@ class OleFileIO: Load a directory entry from the directory. This method should only be called once for each storage/stream when loading the directory. - sid: index of storage/stream in the directory. - return: a _OleDirectoryEntry object - raise: IOError if the entry has always been referenced. + + :param sid: index of storage/stream in the directory. + :returns: a _OleDirectoryEntry object + :exception IOError: if the entry has always been referenced. """ # check if SID is OK: if sid<0 or sid>=len(self.direntries): @@ -1599,9 +1606,9 @@ class OleFileIO: Open a stream, either in FAT or MiniFAT according to its size. (openstream helper) - start: index of first sector - size: size of stream (or nothing if size is unknown) - force_FAT: if False (default), stream will be opened in FAT or MiniFAT + :param start: index of first sector + :param size: size of stream (or nothing if size is unknown) + :param force_FAT: if False (default), stream will be opened in FAT or MiniFAT according to size. If True, it will always be opened in FAT. """ debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' % @@ -1631,11 +1638,11 @@ class OleFileIO: def _list(self, files, prefix, node, streams=True, storages=False): """ (listdir helper) - files: list of files to fill in - prefix: current location in storage tree (list of names) - node: current node (_OleDirectoryEntry object) - streams: bool, include streams if True (True by default) - new in v0.26 - storages: bool, include storages if True (False by default) - new in v0.26 + :param files: list of files to fill in + :param prefix: current location in storage tree (list of names) + :param node: current node (_OleDirectoryEntry object) + :param streams: bool, include streams if True (True by default) - new in v0.26 + :param storages: bool, include storages if True (False by default) - new in v0.26 (note: the root storage is never included) """ prefix = prefix + [node.name] @@ -1658,9 +1665,9 @@ class OleFileIO: """ Return a list of streams stored in this file - streams: bool, include streams if True (True by default) - new in v0.26 - storages: bool, include storages if True (False by default) - new in v0.26 - (note: the root storage is never included) + :param streams: bool, include streams if True (True by default) - new in v0.26 + :param storages: bool, include storages if True (False by default) - new in v0.26 + (note: the root storage is never included) """ files = [] self._list(files, [], self.root, streams, storages) @@ -1672,12 +1679,13 @@ class OleFileIO: Returns directory entry of given filename. (openstream helper) Note: this method is case-insensitive. - filename: path of stream in storage tree (except root entry), either: + :param filename: path of stream in storage tree (except root entry), either: + - a string using Unix path syntax, for example: 'storage_1/storage_1.2/stream' - a list of storage filenames, path to the desired stream/storage. Example: ['storage_1', 'storage_1.2', 'stream'] - return: sid of requested filename + :returns: sid of requested filename raise IOError if file not found """ @@ -1701,15 +1709,15 @@ class OleFileIO: """ Open a stream as a read-only file object (BytesIO). - filename: path of stream in storage tree (except root entry), either: + :param filename: path of stream in storage tree (except root entry), either: - a string using Unix path syntax, for example: 'storage_1/storage_1.2/stream' - a list of storage filenames, path to the desired stream/storage. Example: ['storage_1', 'storage_1.2', 'stream'] - return: file object (read-only) - raise IOError if filename not found, or if this is not a stream. + :returns: file object (read-only) + :exception IOError: if filename not found, or if this is not a stream. """ sid = self._find(filename) entry = self.direntries[sid] @@ -1723,8 +1731,8 @@ class OleFileIO: Test if given filename exists as a stream or a storage in the OLE container, and return its type. - filename: path of stream in storage tree. (see openstream for syntax) - return: False if object does not exist, its entry type (>0) otherwise: + :param filename: path of stream in storage tree. (see openstream for syntax) + :returns: False if object does not exist, its entry type (>0) otherwise: - STGTY_STREAM: a stream - STGTY_STORAGE: a storage @@ -1742,10 +1750,10 @@ class OleFileIO: """ Return modification time of a stream/storage. - filename: path of stream/storage in storage tree. (see openstream for - syntax) - return: None if modification time is null, a python datetime object - otherwise (UTC timezone) + :param filename: path of stream/storage in storage tree. (see openstream for + syntax) + :returns: None if modification time is null, a python datetime object + otherwise (UTC timezone) new in version 0.26 """ @@ -1758,10 +1766,10 @@ class OleFileIO: """ Return creation time of a stream/storage. - filename: path of stream/storage in storage tree. (see openstream for - syntax) - return: None if creation time is null, a python datetime object - otherwise (UTC timezone) + :param filename: path of stream/storage in storage tree. (see openstream for + syntax) + :returns: None if creation time is null, a python datetime object + otherwise (UTC timezone) new in version 0.26 """ @@ -1775,8 +1783,8 @@ class OleFileIO: Test if given filename exists as a stream or a storage in the OLE container. - filename: path of stream in storage tree. (see openstream for syntax) - return: True if object exist, else False. + :param filename: path of stream in storage tree. (see openstream for syntax) + :returns: True if object exist, else False. """ try: sid = self._find(filename) @@ -1789,9 +1797,10 @@ class OleFileIO: """ Return size of a stream in the OLE container, in bytes. - filename: path of stream in storage tree (see openstream for syntax) - return: size in bytes (long integer) - raise: IOError if file not found, TypeError if this is not a stream. + :param filename: path of stream in storage tree (see openstream for syntax) + :returns: size in bytes (long integer) + :exception IOError: if file not found + :exception TypeError: if this is not a stream """ sid = self._find(filename) entry = self.direntries[sid] @@ -1813,11 +1822,11 @@ class OleFileIO: """ Return properties described in substream. - filename: path of stream in storage tree (see openstream for syntax) - convert_time: bool, if True timestamps will be converted to Python datetime - no_conversion: None or list of int, timestamps not to be converted - (for example total editing time is not a real timestamp) - return: a dictionary of values indexed by id (integer) + :param filename: path of stream in storage tree (see openstream for syntax) + :param convert_time: bool, if True timestamps will be converted to Python datetime + :param no_conversion: None or list of int, timestamps not to be converted + (for example total editing time is not a real timestamp) + :returns: a dictionary of values indexed by id (integer) """ # make sure no_conversion is a list, just to simplify code below: if no_conversion == None: From 5db868e0602385319c75375bd9756e4fd71ab0fc Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 14:20:15 +0300 Subject: [PATCH 366/488] Created with ImageMagick: convert lena.ppm lena.ras --- Tests/images/lena.ras | Bin 0 -> 49184 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/lena.ras diff --git a/Tests/images/lena.ras b/Tests/images/lena.ras new file mode 100644 index 0000000000000000000000000000000000000000..b5893e758a88b7fa8f358cb2ee1a4d4fc28c57bc GIT binary patch literal 49184 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 z Date: Wed, 16 Jul 2014 14:24:23 +0300 Subject: [PATCH 367/488] Sanity test for SunImagePlugin.py --- Tests/test_file_sun.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Tests/test_file_sun.py diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py new file mode 100644 index 000000000..c52564f91 --- /dev/null +++ b/Tests/test_file_sun.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestFileSun(PillowTestCase): + + def test_sanity(self): + # Arrange + # Created with ImageMagick: convert lena.ppm lena.ras + test_file = "Tests/images/lena.ras" + + # Act + im = Image.open(test_file) + + # Assert + self.assertEqual(im.size, (128, 128)) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 43dab9113e0088921cddec263d6621089a7c5114 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 14:26:30 +0300 Subject: [PATCH 368/488] flake8 --- PIL/SunImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PIL/SunImagePlugin.py b/PIL/SunImagePlugin.py index 0db02ad25..e0a7aa6ee 100644 --- a/PIL/SunImagePlugin.py +++ b/PIL/SunImagePlugin.py @@ -29,6 +29,7 @@ i32 = _binary.i32be def _accept(prefix): return i32(prefix) == 0x59a66a95 + ## # Image plugin for Sun raster files. @@ -70,9 +71,9 @@ class SunImageFile(ImageFile.ImageFile): stride = (((self.size[0] * depth + 7) // 8) + 3) & (~3) if compression == 1: - self.tile = [("raw", (0,0)+self.size, offset, (rawmode, stride))] + self.tile = [("raw", (0, 0)+self.size, offset, (rawmode, stride))] elif compression == 2: - self.tile = [("sun_rle", (0,0)+self.size, offset, rawmode)] + self.tile = [("sun_rle", (0, 0)+self.size, offset, rawmode)] # # registry From 53b7f6294b157e5b54267132e5678aaaee9b949f Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Wed, 16 Jul 2014 11:36:56 -0400 Subject: [PATCH 369/488] First steps toward MPO support. Allows Pillow to distinguish between JPEGs and MPOs, and provides some MPO metadata handling. Does not yet handle multiple frames. --- PIL/Image.py | 13 ++++++++ PIL/JpegImagePlugin.py | 51 ++++++++++++++++++++++++------ PIL/MpoImagePlugin.py | 70 +++++++++++++++++++++++++++++++++++++++++ PIL/TiffTags.py | 32 +++++++++++++++++++ PIL/__init__.py | 1 + Tests/test_file_jpeg.py | 4 +++ Tests/test_file_mpo.py | 61 +++++++++++++++++++++++++++++++++++ 7 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 PIL/MpoImagePlugin.py create mode 100644 Tests/test_file_mpo.py diff --git a/PIL/Image.py b/PIL/Image.py index ea8cc6155..2aa2a1f31 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2233,6 +2233,19 @@ def open(fp, mode="r"): fp.seek(0) im = factory(fp, filename) _decompression_bomb_check(im.size) + if i == 'JPEG': + # Things are more complicated for JPEGs; we need to parse + # more deeply than 16 characters and check the contents of + # a potential MP header block to be sure. + mpheader = im._getmp() + try: + if mpheader[45057] > 1: + # It's actually an MPO + factory, accept = OPEN['MPO'] + im = factory(fp, filename) + except (TypeError, IndexError): + # It is really a JPEG + pass return im except (SyntaxError, IndexError, TypeError): # import traceback diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index a434c5581..7dfdfa308 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -36,7 +36,8 @@ __version__ = "0.6" import array import struct -from PIL import Image, ImageFile, _binary +import io +from PIL import Image, ImageFile, TiffImagePlugin, _binary from PIL.JpegPresets import presets from PIL._util import isStringType @@ -110,6 +111,9 @@ def APP(self, marker): pass else: self.info["adobe_transform"] = adobe_transform + elif marker == 0xFFE2 and s[:4] == b"MPF\0": + # extract MPO information + self.info["mp"] = s[4:] def COM(self, marker): @@ -380,18 +384,22 @@ class JpegImageFile(ImageFile.ImageFile): def _getexif(self): return _getexif(self) + def _getmp(self): + return _getmp(self) + + +def _fixup(value): + # Helper function for _getexif() and _getmp() + if len(value) == 1: + return value[0] + return value + def _getexif(self): # Extract EXIF information. This method is highly experimental, # and is likely to be replaced with something better in a future # version. - from PIL import TiffImagePlugin - import io - def fixup(value): - if len(value) == 1: - return value[0] - return value # The EXIF record consists of a TIFF file embedded in a JPEG # application marker (!). try: @@ -405,7 +413,7 @@ def _getexif(self): info = TiffImagePlugin.ImageFileDirectory(head) info.load(file) for key, value in info.items(): - exif[key] = fixup(value) + exif[key] = _fixup(value) # get exif extension try: file.seek(exif[0x8769]) @@ -415,7 +423,7 @@ def _getexif(self): info = TiffImagePlugin.ImageFileDirectory(head) info.load(file) for key, value in info.items(): - exif[key] = fixup(value) + exif[key] = _fixup(value) # get gpsinfo extension try: file.seek(exif[0x8825]) @@ -426,9 +434,32 @@ def _getexif(self): info.load(file) exif[0x8825] = gps = {} for key, value in info.items(): - gps[key] = fixup(value) + gps[key] = _fixup(value) return exif + +def _getmp(self): + # Extract MP information. This method was inspired by the "highly + # experimental" _getexif version that's been in use for years now, + # itself based on the ImageFileDirectory class in the TIFF plug-in. + + # The MP record essentially consists of a TIFF file embedded in a JPEG + # application marker. + try: + data = self.info["mp"] + except KeyError: + return None + file = io.BytesIO(data) + head = file.read(8) + mp = {} + # process dictionary + info = TiffImagePlugin.ImageFileDirectory(head) + info.load(file) + for key, value in info.items(): + mp[key] = _fixup(value) + return mp + + # -------------------------------------------------------------------- # stuff to save JPEG files diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py new file mode 100644 index 000000000..18f32bd48 --- /dev/null +++ b/PIL/MpoImagePlugin.py @@ -0,0 +1,70 @@ +# +# The Python Imaging Library. +# $Id$ +# +# MPO file handling +# +# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the +# Camera & Imaging Products Association) +# +# The multi-picture object combines multiple JPEG images (with a modified EXIF +# data format) into a single file. While it can theoretically be used much like +# a GIF animation, it is commonly used to represent 3D photographs and is (as +# of this writing) the most commonly used format by 3D cameras. +# +# History: +# 2014-03-13 Feneric Created +# +# See the README file for information on usage and redistribution. +# + +__version__ = "0.1" + +from PIL import Image, JpegImagePlugin + +def _accept(prefix): + return JpegImagePlugin._accept(prefix) + +def _save(im, fp, filename): + return JpegImagePlugin._save(im, fp, filename) + +## +# Image plugin for MPO images. + +class MpoImageFile(JpegImagePlugin.JpegImageFile): + + format = "MPO" + format_description = "MPO (CIPA DC-007)" + + def _open(self): + JpegImagePlugin.JpegImageFile._open(self) + self.__fp = self.fp # FIXME: hack + self.__rewind = self.fp.tell() + self.seek(0) # get ready to read first frame + + def seek(self, frame): + + if frame == 0: + # rewind + self.__offset = 0 + self.dispose = None + self.__frame = -1 + self.__fp.seek(self.__rewind) + + if frame != self.__frame + 1: + raise ValueError("cannot seek to frame %d" % frame) + self.__frame = frame + + def tell(self): + return self.__frame + + +# -------------------------------------------------------------------q- +# Registry stuff + +Image.register_open("MPO", MpoImageFile, _accept) +Image.register_save("MPO", _save) + +Image.register_extension("MPO", ".mpo") + +Image.register_mime("MPO", "image/mpo") diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index 92a4b5afc..ccbd56507 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -147,6 +147,38 @@ TAGS = { # ICC Profile 34675: "ICCProfile", + # Additional Exif Info + 36864: "ExifVersion", + 36867: "DateTimeOriginal", + 36868: "DateTImeDigitized", + 37121: "ComponentsConfiguration", + 37377: "ShutterSpeedValue", + 37378: "ApertureValue", + 37379: "BrightnessValue", + 37380: "ExposureBiasValue", + 37381: "MaxApertureValue", + 37382: "SubjectDistance", + 37383: "MeteringMode", + 37384: "LightSource", + 37385: "Flash", + 37386: "FocalLength", + 37396: "SubjectArea", + 37500: "MakerNote", + 37510: "UserComment", + 40960: "FlashPixVersion", + 40961: "ColorSpace", + 40962: "PixelXDimension", + 40963: "PixelYDimension", + 40965: "InteroperabilityIFDPointer", + 42016: "ImageUniqueID", + + # MP Info + 45056: "MPFVersion", + 45057: "NumberOfImages", + 45058: "MPEntry", + 45059: "ImageUIDList", + 45060: "TotalFrames", + # Adobe DNG 50706: "DNGVersion", 50707: "DNGBackwardVersion", diff --git a/PIL/__init__.py b/PIL/__init__.py index d446aa19b..56edaf247 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -36,6 +36,7 @@ _plugins = ['BmpImagePlugin', 'McIdasImagePlugin', 'MicImagePlugin', 'MpegImagePlugin', + 'MpoImagePlugin', 'MspImagePlugin', 'PalmImagePlugin', 'PcdImagePlugin', diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 69c07d2dc..3bf757332 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -216,6 +216,10 @@ class TestFileJpeg(PillowTestCase): info = im._getexif() self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh') + def test_mp(self): + im = Image.open("Tests/images/pil_sample_rgb.jpg") + self.assertIsNone(im._getmp()) + def test_quality_keep(self): im = Image.open("Tests/images/lena.jpg") f = self.tempfile('temp.jpg') diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py new file mode 100644 index 000000000..17f9a66fb --- /dev/null +++ b/Tests/test_file_mpo.py @@ -0,0 +1,61 @@ +from helper import unittest, PillowTestCase +from io import BytesIO +from PIL import Image + + +test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] + + +class TestFileMpo(PillowTestCase): + + def setUp(self): + codecs = dir(Image.core) + if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: + self.skipTest("jpeg support not available") + + def roundtrip(self, im, **options): + out = BytesIO() + im.save(out, "MPO", **options) + bytes = out.tell() + out.seek(0) + im = Image.open(out) + im.bytes = bytes # for testing only + return im + + def test_sanity(self): + for test_file in test_files: + im = Image.open(test_file) + im.load() + self.assertEqual(im.mode, "RGB") + self.assertEqual(im.size, (640, 480)) + self.assertEqual(im.format, "MPO") + + def test_app(self): + for test_file in test_files: + # Test APP/COM reader (@PIL135) + im = Image.open(test_file) + self.assertEqual(im.applist[0][0], 'APP1') + self.assertEqual(im.applist[1][0], 'APP2') + self.assertEqual(im.applist[1][1][:16], + b'MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00') + self.assertEqual(len(im.applist), 2) + + def test_exif(self): + for test_file in test_files: + im = Image.open(test_file) + info = im._getexif() + self.assertEqual(info[272], 'Nintendo 3DS') + self.assertEqual(info[296], 2) + self.assertEqual(info[34665], 188) + + def test_mp(self): + for test_file in test_files: + im = Image.open(test_file) + info = im._getmp() + self.assertEqual(info[45056], '0100') + self.assertEqual(info[45057], 2) + +if __name__ == '__main__': + unittest.main() + +# End of file From cc27e8d532e2ef603fa283d68ec1b56896d7d858 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 21:26:47 +0300 Subject: [PATCH 370/488] Created with ImageMagick then renamed: convert lena.ppm lena.sgi --- Tests/images/lena.rgb | Bin 0 -> 54179 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/lena.rgb diff --git a/Tests/images/lena.rgb b/Tests/images/lena.rgb new file mode 100644 index 0000000000000000000000000000000000000000..82552c7586247c2bceaff3562e247bd097c8fe9d GIT binary patch literal 54179 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 H((eBO+M#Kh literal 0 HcmV?d00001 From e29c2d49c0f94afaf801b3e96ed80c6113f5fe9d Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 21:27:06 +0300 Subject: [PATCH 371/488] Created with ImageMagick then renamed: convert lena.ppm -monochrome lena.sgi --- Tests/images/lena.bw | Bin 0 -> 8507 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/lena.bw diff --git a/Tests/images/lena.bw b/Tests/images/lena.bw new file mode 100644 index 0000000000000000000000000000000000000000..e2981ef5c3174ad1f8dccec0ec30944cdec596ab GIT binary patch literal 8507 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 Date: Wed, 16 Jul 2014 21:27:31 +0300 Subject: [PATCH 372/488] Created with ImageMagick: convert transparent.png transparent.sgi --- Tests/images/transparent.sgi | Bin 0 -> 43896 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/transparent.sgi diff --git a/Tests/images/transparent.sgi b/Tests/images/transparent.sgi new file mode 100644 index 0000000000000000000000000000000000000000..482572df5561858412faa3e2980ad57548206cf7 GIT binary patch literal 43896 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 literal 0 HcmV?d00001 From 2b3d655833140972659d8077fdf91f4033a2a239 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 21:28:17 +0300 Subject: [PATCH 373/488] Sanity tests for SgiImagePlugin.py --- Tests/test_file_sgi.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Tests/test_file_sgi.py diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py new file mode 100644 index 000000000..395b3b10d --- /dev/null +++ b/Tests/test_file_sgi.py @@ -0,0 +1,52 @@ +from helper import unittest, PillowTestCase + +from PIL import Image + + +class TestFileSgi(PillowTestCase): + + def sanity(self, filename, expected_mode, expected_size=(128, 128)): + # Act + im = Image.open(filename) + + # Assert + self.assertEqual(im.mode, expected_mode) + self.assertEqual(im.size, expected_size) + print filename, im.mode + + def test_rgb(self): + # Arrange + # Created with ImageMagick then renamed: + # convert lena.ppm lena.sgi + test_file = "Tests/images/lena.rgb" + expected_mode = "RGB" + + # Act / Assert + self.sanity(test_file, expected_mode) + + def test_l(self): + # Arrange + # Created with ImageMagick then renamed: + # convert lena.ppm -monochrome lena.sgi + test_file = "Tests/images/lena.bw" + expected_mode = "L" + + # Act / Assert + self.sanity(test_file, expected_mode) + + def test_rgba(self): + # Arrange + # Created with ImageMagick: + # convert transparent.png transparent.sgi + test_file = "Tests/images/transparent.sgi" + expected_mode = "RGBA" + expected_size = (200, 150) + + # Act / Assert + self.sanity(test_file, expected_mode, expected_size) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 3322bfbad0ed8eb137cbe5635c263f700fff2146 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 21:30:41 +0300 Subject: [PATCH 374/488] flake8 --- PIL/SgiImagePlugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/PIL/SgiImagePlugin.py b/PIL/SgiImagePlugin.py index b60df473c..bf3c18a43 100644 --- a/PIL/SgiImagePlugin.py +++ b/PIL/SgiImagePlugin.py @@ -31,6 +31,7 @@ i32 = _binary.i32be def _accept(prefix): return i16(prefix) == 474 + ## # Image plugin for SGI images. @@ -65,17 +66,18 @@ class SgiImageFile(ImageFile.ImageFile): # size self.size = i16(s[6:]), i16(s[8:]) - # decoder info if compression == 0: offset = 512 pagesize = self.size[0]*self.size[1]*layout[0] self.tile = [] for layer in self.mode: - self.tile.append(("raw", (0,0)+self.size, offset, (layer,0,-1))) + self.tile.append( + ("raw", (0, 0)+self.size, offset, (layer, 0, -1))) offset = offset + pagesize elif compression == 1: - self.tile = [("sgi_rle", (0,0)+self.size, 512, (self.mode, 0, -1))] + self.tile = [ + ("sgi_rle", (0, 0)+self.size, 512, (self.mode, 0, -1))] # # registry @@ -86,4 +88,4 @@ Image.register_extension("SGI", ".bw") Image.register_extension("SGI", ".rgb") Image.register_extension("SGI", ".rgba") -Image.register_extension("SGI", ".sgi") # really? +Image.register_extension("SGI", ".sgi") From bc34bda2b8d89db7d13d11ec47ac63919e34775e Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 21:35:43 +0300 Subject: [PATCH 375/488] Remove stray print --- Tests/test_file_sgi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index 395b3b10d..e94f0d989 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -12,7 +12,6 @@ class TestFileSgi(PillowTestCase): # Assert self.assertEqual(im.mode, expected_mode) self.assertEqual(im.size, expected_size) - print filename, im.mode def test_rgb(self): # Arrange From 1ff695873f1dc9cf880007df2b9b0f17c63a1281 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 16 Jul 2014 23:44:35 +0300 Subject: [PATCH 376/488] Cursor image by Miss Mycroft, 'Released under the Release to Public Domain license' http://www.rw-designer.com/cursor-detail/61757 --- Tests/images/deerstalker.cur | Bin 0 -> 4286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/deerstalker.cur diff --git a/Tests/images/deerstalker.cur b/Tests/images/deerstalker.cur new file mode 100644 index 0000000000000000000000000000000000000000..16e4bfa52e6c870feba0582df6812b668edf2ed3 GIT binary patch literal 4286 zcmeHJSxj727`{joTKj-jOVvby)>>-X#1|Wv)Im*4fxs{@Fw8K+Y|K9FGQ%<~Qn00z zrB-NC2BF3l($*IrTyPMONEm#u1vOf&vS^!-v=o$WxcC14=fD`+R46#{iJN?L)_eZ% zeE)KXVIE@qn2->LKJAY%Oc3_M5Fv0QJ25fw zhOarG#(@{w&yQFv1~8h^5Lz%<^q|vgz+}>cTAfO{BqWHyX3M)hG&H32B?r%fC#s;x z206JIkezFVtZXZ!XP6LXw5GG(570W1f^1rK1f2pBtw!!4AIe%5E*%dtExIa^;K`r2OiJ-3;%@& z&vPLytxoP}MJbe(JHVEo4QABeduOdy4KkU8`ar2tKw@G%gonQd3F3H&h&T+zCB^ey zU0qcepF7se!`1n@n`lsZtPJvOS&R0yDr#4yR#5Hbav5#KViDRsgt{nD6B*?ZD~yG@ z`r2P_-2B6W@f3>iTML(w(cgsir%tjJm8DSRuv7bby%vZM$o=JVDM%zFCsHT}p)dwi zDvV2v2@P4t`M9`PP+`0@InT1=Rs1G&FiqX4tn00u)E1<^r; zd1y4~(2fjsms0)pxSo}rLHCl_XlOh=P3ouj%(b@ouArvw1m@VHU2->_PK(f>-U`Zr z+@EMe^3rNGK}Mz(ELJ1U)k~Kz_F)ZQlkb;v#9Y|ce(}P%HQh*kPxLpN7yE>V0q>8s z5dC8Y6Y4|m{#ITN-6KBMF8QCtKhZb^eVmbLfjoOI zjT6Fy=uuc?pPQV#b6_Q#PcqCkToD1ur?D*&b@(!?QPOrw{$De+%pRBFo@@zSvH)tU~A&%BPQM?d@aWP;o z$e}Sp=E?Z|mgb-F`;FII^#ODcJk2m3+zA9rn%wmw-eYrbZ;y-QVrG^Fq_QM9bofnL zcf!J+gM$ZOftOxBfVD?Ne9yW%UBg$deEad}*vNL6|@!sBDBcsEK&Sq!dnKKPHk5-gVz1MJhw)I>~&*0Eim3PlN+ke&k-t$mXKLBnM z4;;}ITXx-b#e4Qrtc82W|91-y_}mEq;28jxDTO&kfCxnRBN&`xf|nrRv6f&G5#XN( crc{6k?q Date: Wed, 16 Jul 2014 23:45:18 +0300 Subject: [PATCH 377/488] Sanity test for CurImagePlugin.py --- Tests/test_file_cur.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Tests/test_file_cur.py diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py new file mode 100644 index 000000000..5dc096968 --- /dev/null +++ b/Tests/test_file_cur.py @@ -0,0 +1,23 @@ +from helper import unittest, PillowTestCase + +from PIL import Image, CurImagePlugin + + +class TestFileCur(PillowTestCase): + + def test_sanity(self): + # Arrange + test_file = "Tests/images/deerstalker.cur" + + # Act + im = Image.open(test_file) + + # Assert + self.assertEqual(im.size, (32, 32)) + self.assertIsInstance(im, CurImagePlugin.CurImageFile) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 9acbaa4aeecd7a6fed957a7e20c27cda5bd88caf Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 00:12:54 +0300 Subject: [PATCH 378/488] Flake8 and fix typo --- PIL/CurImagePlugin.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/PIL/CurImagePlugin.py b/PIL/CurImagePlugin.py index 4cf2882e2..0178957ee 100644 --- a/PIL/CurImagePlugin.py +++ b/PIL/CurImagePlugin.py @@ -33,6 +33,7 @@ i32 = _binary.i32le def _accept(prefix): return prefix[:4] == b"\0\0\2\0" + ## # Image plugin for Windows Cursor files. @@ -48,7 +49,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): # check magic s = self.fp.read(6) if not _accept(s): - raise SyntaxError("not an CUR file") + raise SyntaxError("not a CUR file") # pick the largest cursor in the file m = b"" @@ -58,14 +59,14 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): m = s elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]): m = s - #print "width", i8(s[0]) - #print "height", i8(s[1]) - #print "colors", i8(s[2]) - #print "reserved", i8(s[3]) - #print "hotspot x", i16(s[4:]) - #print "hotspot y", i16(s[6:]) - #print "bytes", i32(s[8:]) - #print "offset", i32(s[12:]) + # print "width", i8(s[0]) + # print "height", i8(s[1]) + # print "colors", i8(s[2]) + # print "reserved", i8(s[3]) + # print "hotspot x", i16(s[4:]) + # print "hotspot y", i16(s[6:]) + # print "bytes", i32(s[8:]) + # print "offset", i32(s[12:]) # load as bitmap self._bitmap(i32(m[12:]) + offset) @@ -73,7 +74,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): # patch up the bitmap height self.size = self.size[0], self.size[1]//2 d, e, o, a = self.tile[0] - self.tile[0] = d, (0,0)+self.size, o, a + self.tile[0] = d, (0, 0)+self.size, o, a return From 84b13ff1ae8156e248d76a8e5637504056106666 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 01:19:52 +0300 Subject: [PATCH 379/488] Created with ImageMagick: convert lena.ppm lena.dcx --- Tests/images/lena.dcx | Bin 0 -> 62138 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/lena.dcx diff --git a/Tests/images/lena.dcx b/Tests/images/lena.dcx new file mode 100644 index 0000000000000000000000000000000000000000..d05370be8e42bc69430ce6ced5f4757c700215b1 GIT binary patch literal 62138 zcmeFZXLnmywk`PH=ntIp+Z8 zoO331HYEpHvLq|ZmMmwx%C2-{-M;tTo(rt{1Mg!G%LV})04MCZ*IIMUwf0#sygvWG z{+Ivs%fDYn;Fl5jWdwd1fnP@8ml6171b!KTUq;}U5%^^Uei?yZM&Oqb`2YC`{2%|@ zfBxU;5)1L)|J1+#CGemA^Z(Qj4}Qdb!M@?X;9hfY@b`1>EACtFJMKsBw|IQE6&jtG zTbNm3i%Y8<^)+qm7u$sKHainI8Fdxf+3f_sVQH|#b3e!*UGukm=p z{R8(e+`n`G%-tWE-dN{u?rm;!H+Q+)9NXK$+1=!BaW}U&aQ?3bhX(ovy1Ki%`$lKR z2D;ob9n+hwW|P(E_U3wnCFMmW0Tv9D7nK#2mK9Z$max*2;$U&GyoeQ-l$Dg0u>dPB zFDPZj`2~KjL+2`Q>>TRuY~Sx{>+ERnU|oGZA)Nga?gjU0hsKcO&GGtKo;T>va~I?V zbFy>Cx=Zs4a=ki>*JN;eJ&r<^P%0K>N`&dsR3;HAak;vp~rCueGOBE`yQLB`Rj4CltC1+MQGZ?*2gT|~is+h@Xw>0*KT3vjxRK^!* zjB=%psWUZVjVMzWv~gbp%CEUEF_d3n=w4$`zXznh=YHmX$9=xhH#|DCFh4iHy10g+ zODZZ0i&<%L zd2v}`AXpGA3>23ISs4Lb>Sx6Th56nbkI@=v=wyA^VS7(kdq-P$2kY+Y#RWaZ89f_P zIO>O8*DCljsYseBk!!_i8doZN13bx0zrrgr)SrOmAMpHaJ2W&qzr4ie z*~0u9u3|q7=pBGZkdp1)WuE}Rr`YlkfIY+(veXCo{4u_P&$&<7JwOV31&|N^Ivwu8 zx<0~pKEnQPar?Wgb5pxO`^8_udx5igjdS^$fMsN~^vMg%w149MU+&+yzu%Zz+1y>< z*xP2e_OJt>89uo?-0cOWq?dK~c6N7kcMVUDPjpv1#X^mi8O?f~!RYcAu>3%AVMU-Y zSX5G04*RSqDK06_FDotzlok1lgC&7rMM)tmDlG67<$Ln|IZnOSQ{CFzAL?Q~tg{ER zrI&SfbT8p_pW~G7`V4lL+n2NNV|n?({rutrpTUA(U=05R z@B43n_3PpJosCU$xVt;Mc-qIgk=wY#JzA0r#JxRTouRglp3o>88x2)j`4Y9sZq=D} zMq7@{<16$P6~n^@%7TTZtRz@e2B%wIR7(Ds1e1h5bd`Zu;v+_JF*W=CedxJ&!CDw@F9J4c{CRc0FTb)*e z+N@5EjpwDyQj?NX(vk&fOejfZT7%pu*GMx}ViCNo%q%l2)JBO?%Fk5lq)N3)uFxn| zLZu2`M$IIAv4+`Z8>ns6pyi;-G_yLxX0bzT-bt#>5$XNq1YQ8}>9bIx;%k-%)On$PBPk zvrVgK4!4)(7nT&lGE0ksz_YBNq_n8G2;MeWQe0F}R8?72T*!)9Q83?Miin%h5V}?QJcSxSr>L^kJRd=JMuYxA_cnfECLR9X@i{t~Fbw21mZv z?6R4SB9@XMNQ&X5O5@@alhRczQ=%8h6jFprO{Ubsm#bt_u}P~^$Rx^)Osh^J70Z-z zShU#4lnR|VLoX5Pd^W4wXUfX9=ISk0rgH&NSA9>YRVm1jYOn)^N~sqr73!pj%NHUr zbY!1|>lfUQ?EA2-e#<>yAMTr3T8H1AU0e^3-TN>mE({aF@c_d}PL>SvDFeiS6b6bd zkMQY3GDo~8@Cf z5)t8F!`}DLM?!nRC z0XEnV+ST95I$GKqcDTpvC3nkUa=Ls4*~|~;U0y3c{PyvqXP8N=acRvtc8A+$a0}B? zl6VQ}aT!T5De0*h(ln-#@ii)iR;|nsDn(ME2(~F>3b{-n5ld7CjZ&ZxXca=eRH7CM zWg3~72_#0X&Fe8{nT-~!-RX8>cG@)lj^3`KG`?6QQ^?fDY@QWR23NgL@8k7T3YlT^ ze1z}a!$6a%KA}&!Pj1m~DWK9fKH%QpV@tEk_<%y^mjLli*ijJw{sz+u{N^TiBtu0G!Al^cR!{V5!0UKv6ze5-csxYqJ<~Dw;!VV6=a~ z47Xv`k3KiB6f%nww{V&6J?`1onFGfU&JTm9}FhruBgif^z4 zmYkBoOHC2*1T0;~!~#$ln?%Hy@@0InQY)76GX!d3rbHx`>4g+!GsP;AS|t#IDXBzh z5{u30&SAa+o5r9wsqGG{4zAZ49PDc~C1wi5=?an5ELZ4x!ZX*;p1&A|4wmp&3;h8@ zO78A^?!|6slx%aE&8@Fs*xnDj+YgYaki|U!8V5K%Vz5o1O3su*CfVGbul7)I%UvJ;gBpVs%9q6rf2qg-W#mKZelRhgaH!oP| zEdrbXF9<)IpI1~|3Xh5CBfKsW2^ z?dfQ3X>8fV1bu-y_OZ>(+`j!>e{q%Y=;4#^uu~@tI-l9(v1VCwj3&E6&JyDiSZZQy zB1=h1O-YwXg*>T9sMKqeLXl3LnW0o$_~~g9CX-79nGB9os+EdV5}{NeflpSb1Wcqd zc>Otf-fWg-)9GS0PS9=5FQ}Ziey7Q}B81)a3I{wv(egudJAWBFcg^iWmD7jfOTLSO{0Ci{oE(V*} zEM=+0f(dFQGhyya(0{<)VIQt+Bfq44l=74>ad6}+2nHl}nT=kytKe%5Cb~Cd%n4vl}*Iy`3oj;Dzz*((t?oW(Dv?^sGl}F9A$o%0CUOg!gYSHV zZ@v$^z8wah=qC~tb_d@hItAN>&ka9gp4{9d8cosnIiMs9eYFpAgvL2JXH}jZSuhr|+%xG~q zv%Pr*MZUs7p+8WN?+=t$1PclSz|@oDFUa#Tf3Sc7V-_qdDD)$I<`uZ}?ADw>O-pB2 zs5{h;l%>13yQ{OSoi*0XVtUc^dt9Y=xbuC%Qt?}d4j(#wcW2*jDvTS5U}Bvyzr)H0)vsk9=d5G#Zd zjX){UrpZyf}P&n+TPj$b$SdxMgv7^0@*5T6OY%h zP|}hpB_+4}6e%eI`)}Nz$7hzwdE6N!*08To$wdQwL!*;pLxcST`+Z#%x-^l{q1o4} z)aERfoA1fZ3*>qI`To2D01Kg^H0aCs75H3UPj0ri09r#3CW?^iD-0s&GPlc;RaD*F z8tU!q3iZN9yZVucwl>t))Z*Mn#>cXDU_*~BF|sDdL@>fj|jWm zuh@@(kzDULNLRkWGr{`Va@*j{?8-cwU0T`ZZZTwzFjLa^D08_71j&aIeqbedkgq+E zi#~>Fegr}2=FS>X;(ao0BDIvGQaB|7d?$?h2OQhlS=`1Pp`7PA_z!7A2WsR&PWlb7 zd_$oVA@l`)`z<4i`xovBn?r0L6tWD3#H=zhxir+mBH*F!7i|ZDJ~> zGnjL7J%xFN{-V6XU?p;k(t<#7&=2CK9lvz)3_E(kP1~g_t->NnISU+5mu~J zOI2c-CPf3KP@2w@$dR0QUDjNGwuy3DXn=O5Myh7k>d~oA6-$$flg~$7zJ4|CS`3T0 z90rlV{B0OeqLyTx2YeCR_!^a5u0WN&Lb{9Cfd+laJ8hWk;!8j*}I$3ki;6VRScePzCkZZIC ztx0Rnb|C);;Q_BLEG_f~^B@bAu>8h`p2og`#pPA*{q4D}29_Q4<{^X0&kODskX@J^ z{wmg3-`>{Q)!Ny{y4zXXEtqH{qy^$dUveMg8Ata`Sg03f zTA&$a8i6X0Nz?+ROmEA!WjV7Q2CLDc)m!v3C9+k$rg(gKqB2#WNH`mF{ZizW=md72 zT?ji`(&fKL*6=w%KA=XFy}aJ)9T=NhUYgxp-QI-Vkv)=HbfCT83xjsxXTyFM+wa4E z_qqF&^?w|8wj>r2^Zg`@2O;yx(*7;%i@Z1aW@1v5L*Cq5U)aL?r-1em?C~>zN225_ z_+CXX`io4WmPpwE8|!W?FNGq&2D@tw zX?&?pZ?Yg8HRt3pFA@{P(4t^rkuSd>;47@H?6+`E z=$kBSsT!K=?H7vVD!t4CJTv8)!c0+yT&Hn+Yz}vhMP+eVv}%J)Z-MwAl<2EQr^idg z$>$<2T#AS}d-=xM^QX_j5`Gs}<-fszJ;H?|FehW7Z zCk(Q)45vVHAcfSYVQ2y4K4}XCDRHNl?D;}IBB2%xBeQRtOod1z>6 zq&1jV)iX3QHr`iaPRtN1nOvjN>&y<9k9mEpz*p!G7Ut)%lGfbH`nvs==7z@5;Nslo z+Vu7o&ihu#p6xHp%MGv`uRY6wWVW!TxwWaerM07%wKTOi*0ILg8dg_ZUAl%7Blq?) z_t0CvK6dW-*^?)Zo;!Q!h(62h6ecBz3>vH5qUR@HiN28-6Pt+Cl$V;3&J&?#BFq${ z5+gG^OXW^qLABq-3O%KHozCHLVD29Y8um0f-x-gL98 zwR>=8ZhCQP0T##r!ag1(1rgnS5RRy%@jm75>@v2rw@yhv=o7*&h1QS4CiMcZ36-z$ zfJ8gJ!*P4~5;S@Mjyl-h23y?3PRZ}S=3d+)lcWIpS$GS*BT__y+reux=D&a({SWRx zcGHEKN|V~C$OP|~>HFA3Z?(-*-ZL>eG%`?U7bfr}HVsqi3^ph8_1ZL&CgUSGD)j1-<&B1%c+L(^3X%uct-W;K|yvr2;P^+Qdpx2ketsC2Y-ZfH)#g}NCg zx;KFj$@9;!yT?G{H6Z^27=gU5&8|_s1m7T3*~Z%V26jU!9+}_)coa?#v?AiZq#F^_ zeS%lSpLPlz5 z$j{w*`5u2h^UYg*9-rUGN(-xtN?K}1diw_E*EV-H_Le!ehx6t-EoQeT+l}8iOa@C~ z4TBZ$H#K)Ql-Jj^1IVhnnx@8@`eB^TH(S4E?;MFZ_1>Z196tK)k<)Kw*_HCx$asl1 z%fzg5S?u-L==h{K=<`s~6Vez@iqu3Yka&68481|;x43e&=JG&IU8lEwq;q(rbF6Q9 zYNSq>t`bNEVt%?HMV6spR-4HRT5YjwL<)&TszA9$!%Pafbz~`rT|9p|I`Yc-i)2>rn31zxB?cBN3-h9R(FSapc0g|98M` z;6+_eX}(g05Vb*;$a^G2$Pd z2#vIl4sA|v&d;U`c^N2fiiJrjLb=-JGFnYWi_yuD&nQu4RijFyKn=Cb9dYH_h3hvi zvnz4Y*RI|;eFAp(M~w1^4Q+cExbL~280kd6# zRBKI$O%4xs*H{vg`3jv}tJE6I7N-jh1I(A7>#ks)Y*&^)ud28-;BRVct!W%u9$4Ri z47?8f;2XB^#NcZ7B{G%S;xJkaI-|c~zoVrNN_$O5V^d>W9Z}jk*3{hEU2*cnk)tQy zJAM2&5yuamx^m(Fl6h2$VAQGOuCN;k>AaL!VVW>S2pEyBWP;ZMAcfj4 zv$?V~rpCOA;y_V*ZOcIUSlit6?BLSqN`Fd1yqK>_SEVEiHCAYbP;517y;G$(XA6xe zoHCV8ZMKM`Zd~2J6nQBknq7`ch(4K#nD`y{`AAa-!ow@z2X%;%U4FJ!-xeC1n4MsA zvulgE(A&E(LlW#BVD5gzy+p9vWgL=^dr<#2v7LM>qpkytJw-AyBVdEkCxd(pK=0!9 zPiQsxzH?@MWol_|g6hJrQ5z#F^b8YZe+L_={pHJjqOzoS9~k9ps8w&wOpvKCt96-1 zl|-qt#Cf>aBR$1ZmXcL9FfuvR-&JmiP2>ukx86C)P9HlGapKhRGnapTqfc*5PQ4z*PqSH=O|KFrTun%1NxU?;Q*@F!PXz1)783^?cFOJOhZIdg*NPQTSmY$iBnZ_g* zhtlkEsAZxIl}@Wym{1aR=&9u*OMfHcV#L*JS0gT5i@b5=TI|JCAAtOl+iz>{*~OI* zbNv~?@dxhn&8B8HGB-XtGd;hwwu|@&_y|Z6?I7!|!(-T8$2#v28sf=IY=$}r8cJL#B2F-x_hQ2_q zL}~&=$JPvKrby2;nJRq-Uzx8r<8;0nZgM84rDrwtjEs(rbXOVTQY2EPL}SqDnI+rq z^XGYU^9l=?)$Vpee)ktbL?|j7XzHz-*qq|HCGPeb)c<+x3DO6nT=uK2G__s>pKT4) zcd*W`=71u@THV&t(A3aa)6`to)Y93VeEi(s9XZ2J9X=iLn|IEnunUbNK6T=iglL{t zY0=poYDHXROhS5M98Ul>Ha#sv2Hjo4N9L3&6J)BO$yy{frnQ!M1C^!yjZHz?N zY#Gm#cu+Zmx<(F4VNWpr!ZS%fe@iS$-lO`;Z@CZBg<_@ApcP52LW#^_%U{E!dC*z_x|89o>z!a@ zL!FgI#>-S|bW$`X4BrZyB-p{&{2R4Bh9Dy(m=Z*6X@WexR>tv#-{!E7$D(}z!7c>BnE zH?GE)Hn%ELV-pguWuS$`kmb_L_TyuDDZF?TNO*j48rp_3dGd^;6qHD$X-1_&>ojD` z^=?SUm4g+d9$dl5 z?(^Vr-AmYCJD-bR3#Ux4H~x%$pqphvu4}P zK2J7UKnhEm>nm%UE9&YSr&fm>Xm;Z8w=oT={zJWoA3+X&9QF(^qmwcOLR~>a7c8`? z+<5$$GN-z=v97+Zwyv?QFY;F>-+Ax!nZt+9A31g8y%?Uiv7YFU-Mr>kgyn&a_OIHe2=}AIfx_V!%g9e%F^ty9&u54dXYq4*2Z4_O0n=9*( z#6M(PaBPpaFU2Ip$Dy2Nu^W(7O3p=PigY%MN-b9ybk^LcbCDM!uS8wCay8=mg(#uI zmE*Q~hk6HxdwRG3W}zUlkbpKCn+7MwrpITd7naun6PXO2?ezmFA1bC-N-So9g6Xm+fWRl4m^07BC`Ir^rmC)q@v-r?3R6@cV0Hif+$ku7Y3QPu&SZ1vAn*qx^b~}bf28feYy~Ye5%Xd3imuc z!r||k;u3}O{Mzo$){f>1&9S#5G!>1l^>sD1tu39dciC@FM!d(4M4UTx>U`vl!p7FJ zuEDsZnAB@2Nn)KV*ORX{rNrHcO-kh@#Uu-P;e9KsBu(ld#y)Kx}d^1JZS z{Q2y z0`*OCCf`}x#fI5XSD86EF+-tKYl#L~9iDx6r2|T}-Rg7uvV(PjU|mB~WnEE2TW9`3 zB6SDnOWmEs!EfR2{Ca|DN22o-Qm*864~_|N&Ey}q9{2*0Jn>7SYb+< zM3x~z`C6Zk3U^MW&85i?di0ai%WQmYWpRA53)GUxHJuhoqBu2qa`1(E?hZ(>3Xs@C)-_CT+lN-FxU-p{1QV=)iCmg-*d0l zn%a9uC+F6urk9Xa6aU!;YWJsSH_>dh#_ew`Z-z+_iFya66haju`tAXH-@kR$Gq8p5>P*&Dj={%j(W8 zsIM)nY$*$rmRANF7D;@lcB`wx9Pyi9 z9T)rRn(C_?T6)snJ#pmxiT92lig@?Tb0W~ASU zOpTX{5|fh>1?eg2g3J^_Tw2T(#)2^Oor=t00tq#~alFwa^xOOoT+QHe2=dQ$=3thQ6 zrL3TDG&B(ECjIB@{U2Z+-+;>2w)OUnFHTG^&cVCBzsGSqu;n{DYd8wq+r(wxqS=H> z=LUv?TB~pEZOj9MZvf{3F{QNeIbQ#*1?YeoQBN&ZN4~@C`FSO;9ivL+8$z9WM4v3> zNaf<}x$sU&283tkK0zT@iGDn>LY-+;r{u$?h}nMtx#=VTQe?Z)A`S^^_Tt zGK6BZtZHGcHcxhreba8Tg6}%A@(R7avNm67a|6rEcFgQ;FHtWPx>T?)>aZXZKs5J0 zz$8Xbz5oK&$k{YcEmD`B_A=x7!$*Geo-(hls;s8f`}VuXP8@?0bmF~3r_TH)zNxLM zd7y7_C?PIwKOt3}1M!t*>3I+YVp&RJT5@UvJg-C$h)EO+l*t)lk!IhN>(sgP<$9?y zzip82tbK2GZfRy>RQm{h3R#nffe?ow?N+moG+~ITe>?mSiZ53Fj_E zvWpk4vB=Apu0&qWC=NREe4c#oKz|6$-zymZZxBg|?LuAHWKC$-m|0|#i|Y%R!W=Tr ze`WJa)Jn%;odNKCh}lgIdk+8$)ueB2EUbXgkf)`n34HL1?3Mg6^Z~pgs{0hLza{bu zuzuwJ*j0D4}97v3N9M%w@ zpmZe8Of@xjvBA-i&T1P==1Y+pDE0eRyUS|kK2#WW*|uzp+mm0&O6!UOmGxemkMQJn zw%6y@!k+G(j*F88SU z=sQQ)@pBR9&K^E?77d!E?d7#Y$R9#6vB^m{qLR%T^vZgS68??&n56jVosj?n426MM@f4VZLZXW_xoj{P9Q4#+sil@v40Nm-Xse`l1e1@6@E{Z zX;Oy>=L7OeA^}B95^{+Fz1n{Rgei7DEMLPnzlLB$(f8?cmROXLif{jn^Z5p~o*xrZ zQEZ}$kVTD2ytLt8bX zGg_T~Yj&QI1?vhbipz4nS)C|}P0w-f&tb?Qf}xd$I)UMSi4h*OMiIIX!ezbJ+)wwV@G(JDI zxIVhJHNLpBiiKC0hm5MpPd6h47N^CKZ8oGuo?&MrmzIagPd{FWycT)oay+|y?Rs?N zg=;whpC>0!l$Yyo?-?2F3#|i>ub}`^?ehoj#b#N3_t3=r-1O8UGzM;G3FWDu=T}j- zgr}fN=z;Oxp27G&*xi@~imy@fAOW2^Ef2a4!gh;y#LUQ|=|PpSFTio%uOXM*l#wJ<8g&vK>PpJYDh%np9k7}w`;XE^a;YfYTG}x* zJ~q%>V!9TeCc0>?K7;mdtiEfb8vYBwllP`!?xBCA;3oC`6`>k?#z~_o;ela*d7a? zCe@Z9nI|&HfM+gTKYR81g=+>@;ALf4FjeI09UL7W>!YD1I!skhXg=JFxyJhLiTSC8 z#kJL?b&lIuLJ#cM)3cC5_hz}b7OmLYZDhkrFH6pqvl2=j`fxg)$|V5wyn$&?t>*z0oUapC z5xHGaVV>WUpXDDJn`VA*ES6d%`B{}&aN@YgLwP{_rn5PU7CmI zA_m1~G0`c<`IHzsK}tnXTAfDKF)E>u3#M&hCyj7SSb|j{#d(A6R2`#cqHkC66I!rD z($*IoYf->-zr#2}Aw&*Fmi}ArA6FB^TDe3kQyN5)F&vUC4I~ODKy|x^>b6f&tL z*g3++M|*3nF{ylsQm<9%4Jb!h)ZG0x8KfqY8@==T!iv1IEa&*-=-@=h>c-^4%FN>C zdOr=-`}?TYl0@;4Tp^A^Jyq1a`g}0h(#?A6vjpep(;=KAb(~XwDk+>_HNAcO?9n5q zP8>OX=(wY=sg?D0!ybG3QW8~3(Mc(33WvvOv#XM>#742is*1vade+m_IXyL2FfzKlK0V*QHOqZCv;^`)r-frqr)hOYr#Lkw zZg-;{>JVE)eu_cKiHyE+4tZ_F`G_b-d48_9&|BcfI>^!4rJ1pbgHDD6jDL?|eZJDx zH9RrB!qyg6sg!|g+e0+@Je`?cAZuppfEWWpZDIRZWlH5~S~2kk*G`!!Jq~K#)XVTV zj3342gN32wr(Xfb1Lc=;rUU2qJ-$m;PR#iksW!(H&0Q4wsRT!M4JoP{qe|V!#JVV#f6U!%z_Pze zM-M{LBIYYC{UP0rN-VGy%iAYT9XopD^x2dDHNLI2u8;NihB{I4NoDadX;gQ~=T~MLAWzlFHt)_U_6?);`+aJrbH& zo0wl-Un^VO+*n@>_kwX7GPTYu74&ml>kJ0s=H~h=xEN0KWBH})*Uq227lk2h;h-c zzQ!}PnjPp{m>uCR^51fQO2*n~wM?wRGQ3rM??a%Cq!&ja8zb5a>Dq|JwJMRlwsVk; zP4rheQ=n^M4T1?~<8=jeqaCypFC@!mU! z3%lz(hX8p;Z+AzhAT~N7_G+9_<%D2nmM2HDs1#vre9CnehmOLWtn^fBS`y1Mm1ruI z*>n9Y$DPyIJY3Z|T~}S+F&=84?V4I#+nlc(UzuCjT-#ck+g%dsMT!cJ8yW?1T3_5; zTi#`#>|ME$5_#$D#cP*OUv-!Iihb0w9Ps))(+jgx2w5M&%M#Lm2n)+!Yl2c-0W28pMezsK_&#uL%$4AGe z#3m#s@Zv{GP{&D3Nfj}nIzy}~4%(c#mOw*wJvvd>I`cyHp~;&5>F)9J;l9w&=*;Te z;@o=Q%1)ocI5)-mP-0tOSla*vMoiwk5`8`L5<7n-Hc9O-$iEG*5;PA{*mjDUTCWkZ5p2i1csC8-BO3T&7P=2Rir2Y^Jv zNbI1_q8Ip_N;qKy1sgpu>~M=2GFUQsa@0h?pW)lItm+Yd{Q}2+hPg#53z;`*Bjnz{ zfGI=b;{FgNLOv|e8jJ-nB{ED(9J5XDu^E^zm|ff2Su&8@1UaB{d~v3IU~PoWt*%V2?My6gug&k0?e49k zG6MPynP&mZmPt#;(jlxKA((01*ZbiE1LVIudg|oSci%g8Homs4p>qJMsJhV!-qx9v zk{Zuq66F>>)=y;{QZHQ1h)rT~@#thicHC|gv2Ct zU~^Y-OKV$Q$@FY_&B$!W^zQQL;5a~8onG%>YiwO!Spq>`+uE1{7##O-J@U%+xC`f^ zZY1)H10~*)yrSHkKyfbf&P|O?FD(M47Z}-}f$^U(nm=P?x%Q61`Pr$3$+`L2wWTTa z-Tn;2LhxOlXIQe0`U$nbQ0pi#+QCp$OMX}d#;aG1TFAo9OZbHR@N4!Nz$RF!Lyau< z1(I57A*RI|4*(|R%&)<>$Z8KFHbrByf6ATz$h}Av$<#8lNog3v?}`3C!e<0B)%3}{ zXtI2&R`T^4mA$HCY-(<1v@VO6CKSt+3{CKAgHDcB@l7SgMl9nrmlos(8!M_h@@ktJ zySj%bX9h;LCg#WI=f;-N-mx~1PT95Hm96>BrPT#0MJ~g1DH*>HE=qKq%=iv&XMiV) z$9I0s4xc=H>CDMJr7XK@-#EU&z{WWrxtJy_CEJygT$+s4{MjRW(Ob2IZT({q!9y+a$d z!wWl`!wZW`8|$l!<7nprdtw*S8IyAPLXwBlMXcW|!759%;7v@zn z_96~bZcLrNWYol^DfSZ92c0xz__WT76tGY6tuJA+#N7zzSD@k)mFa#Eq{sa1N*5{A zX=0tc0KcK>1t$W(PktRyAKVMCWN6aVDh+kLqcf9pgSD>sWU&g3wh&IxouJ@8Sq#-T zTMfC^ERUnOyrI0w-Q3byTr;yYP_?))IX$^Jwm1$}y98dlvpcoAHnX@si#`D?*9%8s zSne$#{C*f?)W?P2*cbN9%rbHq4Sj+~qIy)=JoYUTy=jtr$sH*6wXlO#M zs;z6FbZMrof4I77ptobUeRgScZFQY3E+ZCmtgLcvb}s^9H}UlOYd82Pau)gB`Nc)W zIc|^73*l;NVGf2)#O7ysYO2%!j{9n(tz~!u7d$gJzrQ+))?8{nh1L)8V{DPTLo4)& z_&(-7*ukh0f|NJDpfnDfNwqw<7spGCKRULt^)9|cjcwEcOiY{_^r`WQZ0~zSWy+%H zW)H-{$(p|azj_u91|Ih|p1Jc^&gW6Rtv@N8-|`EU#3@L%aQob+tZeDyB(>O>ZANoZ@Y7Y7g}f((MFT zZ>YN~F8M}M+_hAVDLb(5kP5D&a+H)D%i^JsBqSGm3@T+>d`dD)*E=L8uhQ+$%Pz_H zbQd(%RfK~11HQJ(rre&U`u369&Y}9D!S;ckw(jZe*~P9g%)8a)&FPx*R{P}AwM&qo z&qQ2Q`tz~^BcUv4#bj*d;uPffB}wl+P3 zr1(b+ISCyfFOQ+kn=mDQ1Gy3omNaR)H_JhbPyN9IZ*0i{Dv@EtNQ>Qbbw)SE%Q zs88^#hdAg1m^Z0eFYtUos@~vuWU}AF9XzAHv(LGYlLTs6hE8JZ!0#U89B3~@n+ayx zA1!1Luj^9D1xluJRP~NeOiv9rnG%yE40lQ(vZYgKx&0Z|RFiL(8q77d!Sd>Y`i92( zo{quRrqNK>Oy5X<>%vfPXlfP}(e=5p>6zvJG49R+wS-YUAB{uQta%$4-=$T5opbpE zYa73M`|PoIPanRlXl-u}4fS;a+)mch*V7e?0%2l8e5TIB+#aJMF)I2-S~`nOPP{>K z2+LE06-bg&6P;#-UafQ5f*yBEZb43GZb?T$No{^(xejYY%DXxTnwq;iN9sC8Sm)xx z>cZT_Jl3LaZf|atHJBT_rp}$Z&dy&t&okr}6&3{wa*7Ilxmj4Yna9RP#z&@*;SjAM zqebHj{=QkmEfv_*47zKUm!`+zhyDz!BGu|H0Qq2*Qb8JIqLLqlp(CG6RhNUMbL4vo z&L`|47&H|Jp5d9SjM(xsOo~Tg&vsBwB8#P3A=x_VUv6$opvChpLPP3L5 zpTy!5;$jl7C&eVjrqVq`_&RZHk`YBgjV@Q|Gi9?}w;$S!tG=YEr_#YHf*oaD1EH?Q zmeTr$;nM2ymFdvz%>3la=K2Z>@tfrhuGaGPEAbbvBt~3`l6d?`@&m=eTwGm#j@Oej zGBY(kxrpvMj~eYM-#j@2#GvrDs+vr_{Y@qawnj`9DJyGzxar?4NQ8VmzZ zdEWz?-<0P(hX(x^nBQNS7@}pMbe{l1^&y_gJ3m7G4W%dS2|_7MnbhioX8waF#)C*p z`ZZR;!(!0$F@+!gJ{EOf)CV=KI5f4_eTqFF7%-7%7&7)tct4h^<>+GZ)bx+BnYn>> zCwf)T)h5OII<-R3#oe0gX)bDUt7@yTCbFQpw5`cs*;WILTkCsUs@$1vT|?+q8R_n4 zgHs*t(E8ECio@-1Vcj;Z!KQLE-B^e)rr7_%NKKbzocrSM-aUKr$kpSgl^re3y-1N* zS9hofw@>Tsxse=~6c@{vSY6qWCU_xzT4*)Nwa2 zUXF{roN_Tv>kW8|0^Wenh1K=`y!=4+*woC#0^KbOhJ>;Mx#&M~Z=iAvV(AK-U7cH* z8vt;B#&%kY^%M6Qic0u8u5ulJaIYGw1e2WkSNpA-nrL7ljKJO69YS{=lmFS20VQON z&X<@0U%^|@iXC#~^q@KfB|_iqe+T5=uMBNts@+TwD#UV&#JGWd90URK3I}xkz)z5l zM*RB|sR|+1;I8SL#kEY;o8wbND!ELKW^$D@y@}hOLt5HXVGrgOH~MSps_OGfYO2~= z+S2l^876r{r=+-lqOOvS=bq_ibsRTI<;qN* zEVBg|Qws%QNe}YAe=7#i;xD?N$YYIKgO-$>n%)Vpgt2yKLVUVPrp8iN+**!b2!mT0 z?g};7U3o=$m8`rzA9uW~ZE18?<(j0TsL`Q7S#gvkOL-zncBp4(fNI0kJdc$uR6q<@ zns9++rXYTy%ydaEV19Y%?W0HDK6&(*25Th;`@oC4dgvx<-Dm({>1ik{@C3$eXO7>d z=3R@7N{xY%ksKX&^?FKm5o$>4Ox(64%kEYi-8QYu>hfg=oK~|zE9tHu9_*EyoyK^T zEw;Y1bYOV0e;T#*na%aZ)wQja^`+wLF-hi`jmQfZ&z-q={)QgQI7`b4gT5Tx2E&Kt zyv#ctUPeYm`fv8hKuOB@dbhrCeK_JXj*#J@9`Z0KedV*rR*)K<>scgZ=|Z zYLcV7YuyQPx26Y&=9()*BV|@iyV{kRl9td9CrOG1Qdd+IzJh5U+#rIGBv(s;^c#+A zYVYb_fZzQo$P=rSe4|iAg&la?Tl90vD-UwmzuwD9rdP?eRu?p(>B;Gl_Ux4Sbg4$J z)}dWjlc}b>^Y&16hsWs(n2IV(>H;MdrIn3!+R6ea?un##R0(4u0uf0^-g@s)C78*F zKo9HZkl0WkE(y&viJ(aNc?yxjxy(Jhe)t$W`R+M(q_m~3YXrBC=|>ptA^1X_@hp`k zMkR<v-k2P7g~g>rC-TApH7b{x_!6;FF0^0?n9YgR8AeyGkS|z*|6gvNmyN`JU1m>Tzk((%f|#z0cnflVS}qL}^=j)3wa^1b(TYMsht_tgwc zjz^hD)!|;1$7mrxn<=Q`E}*yih$E?i@gxZ6Drl0 zv?n#E{?A`86)zBqa5fCfKIjIQbTwh;ihN3fPGX4>k@e$aZyh>$V)-7Y10LJuYb4x#ZFy>~a z7M3Q*+qr*X|Bhh#65qp;kFiyZB@B|<&9Tx3n~<(9U`69}!05Lavnva9)g^vW{dFbx zdI4bI#%lO`kcz(8KPctXy1B1MU7A!o`T|D&9Ht@*MO1*UKE|TH$U{{}AcKDB@Gjf}m*#D)?es%it>a!QtcY zojQ5w@a2SKzV>Q1JTfrc*FD&c``q<*c67F-fy&0G^0XFHmdD~zB*mfa4T51pRNU3% zsN@-6fq<8z)u&*!mESCOxRo}SxiF{5Auy%!bY4Shfw?NNH$pjNA0Dg;O`wl@d}3~X zd0}NDy#V(Lk$Y{8qn9pYA#%ipcxj3=H>;|sw6eCKq`VLd=yRC2$g|842DdPTufvq) zN3=>2epn&a18sPGhE1<8%=W>O=>DDm9ERcP6mTQ@@gWEe0KBt}Ax371Vp6rX9>HAI z*(eni2Q#8PChp4}%nU4mgre{n9srNM*nhRS7Jh%SgI4j2sJtte*!)p?WcpC zrRJQ5nqnJne&nyND7TkN&e~6$Idcy*Z@w}KD$U1?k*zI*?doyJ!r`xV_nRIry9yb`Yr8Qqqi%xAeM@72=SK~`WrM4;7 z+cCrju?S;$Y+`2KoaSsOM?bQ^XyCo8SCV6}ko;;C&!3Z9gIn18tAoY0MR`TRKp`ud zTV5HT!#UrBmm_jReKNm?F!VKdyS=G@2sfFUnVpAV1W!eGb)At1vY)*^-r> zkSW&4pzo^jkcn_aD8myy&6bjiYISb8FQ?X>RAs*unQ`UtjdyP(9zUC{e@prQ$a@2j zbJrHO*vj1UI$|Qc=~Bv7Pj2hz$OPK*?;UyT@R74;jvs#S(iul{QwZy1N7+yaZ4bRY zz1@IOm?BAxjgJ!0VQs zSk8+QUNK3cGFWwF%X`dXs~>G0oar2H3k{4<&JWk8#TAq^m3ci`O?}5B&PGIDjyQ8E zIo_I`TMRiDMi?xr!2N1jFyG}x0d;Yms-%P>-4uXYWPXCp+-YkH4NuNbP0ugPuC4xm zJpG4Pc=vDbcbDgR_Q?ve)c*^AgjV_% zP6mBU%FB9>Dt6Et>r>a1y=P1>kBjTN8XAXOmTo~L`QdYXIUTcH$3e<~p}KWn0weQ_ zCqBo_>GC|D1lnl?t3w5%bt@n(jaqpd1*3n6ue+8?qM!>pc%hXm751U3&ww&?K|88d zYmBaugT2GONejPRri4~1@>R9AOGJ$YwBrd35rK9HZh$!V&w*%6o>*x`{nVBYv3i+TDz-f~@R&8R-R~@z^HW z+1y>*z(t6n5!c|{Y@JM8MaubgokoaN+M9)CY+w|j1X@{BRgF^PY|9(!s~`p^FqV4M zj#e%>s4-R9TnUd&N)1Mxp)ApBTHZh0Sz2FRN=$Datt?3de21?ObrQg`h3R?4jB*wy zCy&K<^%IxJ7m9cXe4b0cFG>UHqq8R`#}Dz?{Rdp(Ujx%W;@sb#NX%~mYkG1@PM$q_ za=Z*D{42iZPp{wsA76z87aPAk)BQ-pW^_IcL5oBft&C^Vi30ly5f6D^5lN%jm+$P=7T%;a`NN@ z{&}*yzJ)lqw!E7e^t9OrM6AI2wU z){eIi&yNmIs8U?=;W}=||G}SPF7g)D-}vy&htJPfr~KiOv94x;r_#<2lT)vuRXX?>cnzu=@!RA_(@R6sbe#9~j7r|p)($1%W_5Q%-_A2k9}#ou zl!W#LukXN5uOvWdJxIX=-y@wmO}k1rw7QRB(Ee=k##vUu~ z-^;q6n@93f?&YTOti-p0aJjWcHr7@FLAqR;U6_*9iEAYM8g-Ao*U|?S4t5c%iij(# zYoxH6u##tSCQJ<~0AF1_y+Z>|m8RPzu4XyKjm2!K4Z=N>DSJ&#fcRG9sHpik=(gb*}u36sPq4YgJ!ts5M zbOmiYv`{H_`(JZvDiMpDu8%;Uw7xw!hkNdHKVhrj$!ptMhzc6voqcwLDgcYWb6|6+ zoDTOus4v*ZD$H+YXJnSPRi&h5r9Q~&karI6A3ZwVyEr~QKix;Pq#=_Q5A)5u9$|1| zNb<&=G+Z+|=_!EY^M~vs&}P|O+t}IwlxS^f88U)X^YS`LWmQ>aZF_%LzunqeM>v&$ zZC6)f1|pQVNd&^gsExErky!P18TFPrk;GeHC2WeQm?dQlPK_%9#huyZ`H9s9fY;`> z_D+tD*K9nARTUX^lx7!{R5D3vQE^cLx4~)bkN5(fU?@yb`31Z|v}5`$Cl4=%C1|Ry#I@#zTGAJi zXg!lMja0&)9o>cp{TyzB5f+kr+B%^w@?UuEKOv2!tpVt-G2Y?%aMuCm5W5c>dyImux;--v+-6Sm;?tGl~p8$GeSviWp#;2-s3@U zt5*x{^6Dx+O8ANzK9pRlDuvryHA+nz5WFqD4Q0&wa<;18%n|uUi_3Bgm`2H7Y;taO zX>4eEWdOsMqg`^ayXdKAuo}C)5pYTwg!HDFqDE$AJ{BT(5w?SB} zQ2}8)ZD&WjQY*KhUr!s$*T^|4q5ZF!6h(&6F~+qr8Iq&UjuvfeW9Pv9?&0zN&Wr^N zT~MLgAV1O8tvB_wYvAr5lXioxH`GI%9XA?M@1$l6d3loj38C!Hcf)PVn-LI;lKbl` zk51PQX)6@s&-CV`?{U#xnI_~+E~RItkO#o+<=i3R~2(7IBf>U3HhR!fJdvY3rajxVc{@IfrCt*DwGmGJ9(8v7LO9r{88IH7{>dWk`K zs&f|R234!WE0fzh@zG(ZXh)a#H}`gr9zPtf8{S9IF zugNd=B+4N8Cr$Nwpn1BWa;L4IO}=xEzeJ@T-$5-$8&}kEX$PC$kyo8;I3a3vO}DPK zr9l&!LkIWZDDDu7q>XLZ%|JkEa-vvPCx3m>Y3u0-*Z{9;NNu=FN>U5*vxEhPlpFtM zFsHzfl<$cPxd>?q8_paXO^W-}b#m)NyvYeZil~a(*$Vku1N$Hqy%`TIf zICd4RuWYPstZpu%(3_p&%j@_8F2AY6=x~{gt@V`^oEmVKDg}xfsaP)M3!evtI#E-r z5-@8OM_;6<tVj`O)XgU%MV2e7*&;&yn4NDvyaHfaBz9UbrQZJpt4=yi7;I8g??IW@PsL96$R z`>-8i<{nP|8g)li z87V`U5~XN6?pyL#@(-BtH`~o3>s!fB8zBbY(QQ;UwGuV3y?Ev8P)pr`LLg})nQ|Mi zGag#v{r}6o?)Em)-k=RHLA368XS$a_x&lDU_LgQ1VAEz(&)iG1!RQW!+Z9|i zwiBONqH=y0Pq5hIUCQI3{AW>wG>P8Kn>OO_0vEC$%YH|8W1kfuy zMOT|m;FDotdera}HE`TI8=Ld9_MlL1u+f%Gduc^684?*b=DfnGG* z!88Z606?~B>xv#P(iU8(UnW0o?`Ubmt`pxPWVVlXXDmdBWf$7EP94#P`&=k*`;tHL zXa+*oMplOM)}8JmR$5tF-n|sw8y#6g&h5jO&msrO@9nJ3tnTd}P>}Jz=7KZ=AY|`% z<=wt3+!v zsv4QNx>iyrlnOerYC~4je9>5A=uwN<)%^u!<3!Arm58bf)ZEe1mJSl$+8v8O85^|t zLlclqI@&onCl_y?glY?lizI7xr6pyh9CitlQ7H2nJud%%8_3C^$%BCE8MGssI&G(D z6q3CQI0_{f;1)#A`Bn10shQQS)3cL{Gx8K%-YY~sm9_W~aS|!~$(qqC=<3ndb_XGu z+^RCTd6DGLo}v1tAda6M;A`mQh0cs9P@R?sbb-$`LX)ER{*IR0|KN8%NYtV^{ZqfH zwF`M?3(+XIKzW6w=mkidDs%{0(MB;q79{?ObNT1FrbrnpaI*izm;X4?Wod&1CEN z5KF)4-~nJ?ygGeK@c2A63-8|+9`WuM7ZE0SpxGI=k-;?-3zzE~SQjulo0uGpW537j z9OR4nywa-XcDFBLCKg#WPb`4j$t7Y*y-ZqBU9JU7r9algF|wt`I!0Z4A?gWkw>uWP5yNAvi<=vC!PY>hU8KKjh+gMp{(yn+w`X85+ROY5KYjw2rgr!>CbzT^Mg>`^5s}W|5&7%YRrtR! zK#QDFWE{;y>71JOjesig@^9P|oGUUa3A+R_G?NvLOm1wiFRgBFLl6q| z(AfBBe0(gPm=_4e2?54vJhR1&m(pk3u_DRc0tHQfW9` z-Z;BKp3St^#zq6<2ZzhE*1&LdluVGR1lb0v;^B+)mB!M1rfR9KoQNu`i^~`+iK)kJ z8VFlL-q3Kw<@5WjHWTW;K%eXAWaso46%eK1evL!^1AOF9xbxndnqJ-pJ~^nU~JIb3ZdDEwACWU6p)y z>)G5Un2jgpZDv<|i#)=x8jl3JW8~_>MFZ~GkbCon>RA&o{1ru+4>B_vhkY}EG2)-q zFBQKb1t zxCiM>6;P!&$;D$_bIn*96k6IjK04W3w2B4d#x}_1cWS##2BTxmP<(qNSS!i)rz#A$ z3abm!Z{6swX}DA1TiAd0<~&5@V}sKPcU7@`^yK3BzzrL|jh4+Xk4#$tk3MehSl3 zv(XIkTU=Kk#g|sLPfkzB=?UgXZ{r9l^6c%gnrAyRtpiQQ#u>g<(qtDZYXfp_@`o>A zGm4L(>v4Yd^vZGQf&V)B`T52sPX9V|QsdFv@Ar>Zkkr1vYZCP(KYfDPxdBysYuAg% zctB|4fYbzUNCOyEp!mVnwhl_Sq70k&o}8YaX*)VQ)DpF4dglN$<@x>^uyM^$#e{-M zXWzie_)^o|h)R*wk;zXF->ABsTm9WTce@{aXW6%Vo_ufjEcsryhYUFvqpfuc+s6Je zMTwEeJt`!@^#!-t>%Iq28pz8^&ng=5PpqtN%wJ+41c#fRUBQ%nCLRs>vCU6h&a4(2 z?Y$n8y<1WZu2yHAga}17{Mv4J$FPz&BooyP*XTJ*jCLkZm7c@yE6($p9Fs?oD-(cT z-4yDUm-8F_L!MV$;j>GMWRv=Wa)6>riSe z#{`p~^`k47qcc5PoHQ4rj{}XwKYD2GGCCr9i3x}cySAsJ zxlJR9yhWi=|LkMpv%p-)EUEE6MK$)pRYium+l%K9J0YpoC{+dK4uJ33UG!Ji$W^Vn zR&BFJtB01?!q%)KH$^E@^E$qxys5a6YPn5PZWI|>K|F(K8L*cEA}h_qfX_Kd9OG*n z2q(ZSy%_4w&&(sNjN81qUQS^V=6~ro#Z#f#%~gtNLA|$hISXR-{PZMbg$8OX1=Yw= zlbZ3o9q`3-WR_EyD2d+Qr;1 zmtDrn&Lo9h#zuLMKY%#7)FIE$E@88B8MWi8Y9I_5q>v#WGP>NJfYs=*`ukl*hppd@ zTF>DJPGXPFHfC3FzF#4c_!gc(fBYA2mdTm5-N#1&%Tm1c*H_v9SFc+9yna_a99?9& z+TC^4wX>2&QiY&MFEol(#H?;)u%o*}V_a@f?$V4De?VXPFTfiA?TJ`4V>I1={Z9YQTX$ZcdbarlvITEemZoN8 zr||VDx9bK1qlq1g+xXCul~G=tR#cGk1}71!$Sp|E%w^o}8H%hfFJ8{ftpgJ}H@!%v z=jSHJhN8oRl|m6g6jOFOta_`nP0B4NTvExyzxCtV28+(Y;<3Fw2hq`^0<+l2%_*IS zvQI(#mTUdV!AcP$JD-);Y#{l?YE7>tG=I7!XR&!D#hjwN4AHQg3h9-URkB+3Rxik( zb~EZfyW7_9vH~LNa-hz0?rv`Hti@MwxL+mzLMZ75aQ);*v8i3U_T=tWqWVUytPW$&7SYpV{2?voex3YkY+BbjfgUFu^)vT8+=X^qs50W; zF+BQSi%cJUs~1ZSYW=ueW_9}JCbg;^-8WoV*O%ERSE~VOXrxK(HR%v?;^Y~5a9vR)h`~G+w66;lyxesz5+!PLtbQLh~r{i~`!;?S& zPSF*uWM-bsk(tTqiQ(wrfInDMRaRZjW!AMDy2C+FqkvroHXKh_$>miHXuDb)OaiXC zE?m+b87pSg@K`x)Mv07TQJdo%k5?<0MY()gCNKHkA)DVWYt;m{E?Thf@Q& zt^Frh#rSZ-FpLui7O+z-bgCfL-y4qAG|20nQfQ~5>jqncZwC|oY8NQ?*vRAVp#_A( zKf=mCxXNNE=kX6f1An_QlNi}f{v^`ep_Pq8_in=5-q_lv2qX!8)M?|3wz?1uVJupU zQ=lP*seA~G7u@jW4Bxe}4arTpY|f`NI09cB0dEROQ$1oG+$k+>xbL zWo1_?`tCx*MQ|tm{=NHYqOW^wNNx7A0sI6;rj514eIhx!OQphc-U5oCYZ`L(SE6cjLqM_tRkGqs& z$aA_*&&4gehOuErb}p;P&=GD^g3`uj$lYcv5_B7!{T{ca-(?Tzu}j(EGFoh&C}ulb zOYvzO+JB<5`r}oG_nYJomR1kR!&i?c^wAUi(I1olXcAQQ4m32P|JgF9k~Aor4C?k8 z8KtLEmJ8&y_i@Uf!H6GjL1*?Q3V}Z%qW=8KhiE$*>=u;W-|Qye-`K-f{cyzDSKl%? zp8UuiP?LsE|H_0iX^8n zk6ly1&1wia&8yEh1&y5svp^k9oID@ykz>Gb0+IN%$~=B>;w|DbO1UcalvKvRI;c`r zm(Ayb%bBoH*<^K~Xd})(e;;%M+#`?|U0a!(#qpwvi7-jM?zcF-_46mM-b#KB)XtaW zy^Ut^j7QU0L9C4fW0Rc%i9)3&E!7hn2!U7|y?+(bC=%?NCfE}p*mN?0pk^U8~AJFIvOs{w0+kp4^h^L5Th9Nhn{ zc1dN4+aVr`h1JC*pHWmd>}e!YgB$7C0> zxpL*Xs6ZVuY$25pNO9G3_QMNf{lmgnM=I5P@e{xjP9P%vNF zIDr}n%@Zh;{d@gA9%(~8^yll<-Tuhrd^6Os8=BeT|Fm&p^i0jN`+{2dgewi`SR@^{lyD-mCCKIO58BS1lUYj&`B0 zp}iE?`~I^xFV67ibeu$c+280|n&q`MJlD+L!^clfm;FSdZq@6C62rTME54PPvozG3 zjd5kCHJp`lCnF{G-oL%JFaqVr?mkcw!=&m|96JI#I`Nkst0)4Fufnss`rm z3;`ca1y@XjEWmHg#smr76~z56OOIy!X{Ab%}*~dIlS{{QAti=I`{&i zI8|yaTE9_2GfFa|pgu}Mp?M26D>dG4;nRP<`h80Br|IH52Z3gj+m!tI%&@AqO}ap+ zp3n7lcfG#tTH=T@gT99g@11AoRIlxN?e%p(^!Lq8GNG8waZT+2DD`;WTUXzt)AtzW zdOP#9jMM_9rA&A)SIoEd_6(%ozjZe^{ieSS)4GrRYC~jn8cS%G*H-oq5B5g}20gx2 zy!n^Umu8|NvqiaetLmUu9N#A<);!P}z zDd!d!)@r~oAi-u)WjVJRos)jjE(+K26|Ti515avc*BOXU~5fV_XyNi3IT&I$Ii`FaGh;jJu~(qpry&Ic4mr z{t7`}0Ylek?jNvPLd1nFlADZq25@$C+(xUrzv3qR8u#GB z4y|K9!xYF&L3n1Xd8WBhC~fG7nX1lq8kIP~N2g$J{!%)PM1@M4AgDrq4ike*sA>4?si6DR&LtG zSWZ@UTGrSA7Q4^Q#HWyS#wJK?h=jJTT#z=bDJdSmyvM7q5HJdvG7VZ&URRHzoXOG7 zbqyG*bCBqO&ovyLoSln}#iDjU zzK1JmAvB183k9d?ZEzgle$wv`8k+)5)1%l>-B72p>)M1n8%l+Q-++7`M9doPBC?Lp zV5sXLN}DGXVDvR59>D>>x|$2o0{yReS-M+`vI0Llb{M<6G%)2y%@D2{gZB}{W7@Z* zn`;4xhUI7o1*Z2(*_#R=ArfCp=b^&+rd9wzWFf-|xixU7W?XVnBSNUXM&4AGqs|HS zmhAbC|Wg zW~<%lSBZG#yyiN!x!LMcii5Rhp;{i7S5hsjVQ`41w33xut~GD07v)sS8YKEYYj7;K z8J`#)@VI*WG|l}Flbs~Sj3)UsKG8h%>*N=Ub8F~gzO%YEJrar4HkpHp7MWVrAXj>N4_@wp zX!_zY`RbXlYZ)Pr=JnUg*mUI{WrEVa^Y36&DuF_kZfMv9BpSa%1y1M%iH##mq`gPi zg#Y_gF6vPqT}8| z;3HyXhpx9S+}~Jh%+8f5GqO@iW_oriyP(hOxc8m!hE3$1ncEL)k;H|^r{n8;=cnXo zb7gEK>hw)KPyXt~{_;dLWR+rSkaG9o(%sjX8RoHxt%XbIT_oaTqm!c}fx(9~AY8c~ z-4XOY`gY$hFRg;KU$GDZ%Mc3csNpN1H)kUI_t~9Psm{}1`BdZ*{yn`+hg~6 z`)yvgt&b)&R&2<%`v$_}>kFF^f5h+9yWoD5HbAczDp2%w@<$8t3xGT>Ph0%klQM^O zVBV+JHUeH_F36te z|C^Q`FxnNnJ|a{1XzJm6?{?OymGqgWV*?60qI{MQ3DpQj#CnfFVeLygBEF<{{4@DO z6)qljc9AWPtL$uV-Y=~lRBPwQ3&jpsimJ}&mFMO?xOw*`E59fr^$Bmk9`M7VKl{#& z2hwhrCp0v(gbi_rN89VOgP|d}D|(EN^U>x~d??h-tO!upq@a{|u5N{{`H` zZ{dJgBz={QQn5y=;_|zz+x3?yCMb#FZF_BBL?eKi-j6C8y}0x!5u2J0)jRnRzz1Y6 zz|Fim$4mb=Y<4}r{WGPgi7SBa$S&?rY#&3iW!5LG@OFg*(sFl8VOEPII5uCLn|}8W zTbt4LB2irS|1)?1@Vfwx;-`-9f^z~9>H*QBggmYhosyWK^62^i%RM=CMTAhwn*5_ zwjS41hbHEdNg}R;vnMqbOj2AbV%v9|#pPM``X@*E#RXZMO0197`5c4Jp+F?+arQKI zHPp5by?haH*sYzy5>{blUeRc8ZEgV{s)ja)!Rq!|9Zs`_n5}>jd;IQ+DWuVBi({if zpOqMJdNlW^h&>uVf1mtvA-=Y^^Jv^O7;5zm`kX6@N?CJVb9CbH+}zKvmM1@WmHcLv zU#ASLQ$6A<>0!J8)uE?1;qPF+|3s{$-Ir^w!G9nI(prT^R0{F@VprT`3U*gf|5~e8 z%tJ(g&VXnzMz;)C3*>v~v;Nl0=QRJMsmm3h`;uO;^mG1+&Ob{s)2uTarvN@Ldqg$$ zt&Txixw0v*#nPml%6=myBQ>L`!C@&Zed9a#qW0v+o}0JQSz@it8;VTK&MeKYtZpui zyImtrbBMO+f3Y|lkNR3~-{+KN|NBVDct1rmI=Hs9NM;u|5|fFkaT4|I<72%8|9b}y z9_>1OpZ3(D>c?jkWXe08p?;^WO~7K8xA>=Z9j-uqcz!r^UXsUSb9jb&eSJ|ub}FZ- z?WiuFSyad>Q(~i((QARQDpuF_Hpq+((#}Dkn_W(eURA{?swm6Na`c&tOBLO^ekV$a z@EAcE-w$l3-Hpxh;rZ3sxs|QW>DX}CMRYjK|GYXLI>PxILfsE%V&@kpFDzkLzj;#b zn`+W2x+GPRS3sNe)+)-9@9rHX-`7=1)ro2RWjY+VmR&{X8vaJPvGfNOv40O9NR`2; zsKM`$&ioI4{RM3HljKKZ3XOZPwdN%G!4r*`N<&_E&;V`6ok$xp)SiH`Kzr@cE4<*} zsUsuNx*iQ)UE7dMV>46JY|}euTV9ZI`cchmEoLQ zp5Zve31YKTmvfU7iKu@F)cz|&QZga!^wA~+TB^b_KBKrWN6~Ew^@p7r8LJ|y?d(>Y z{5nI^sA6nKB&ik`Ww(q~>kD&BizDr=qdB?RIc&%tqZ4T8?zas%LZ04kzPhVN-tBvs ze81Dy*RCutWV0~T_l&yO%#K#0x!=@hzO;wjW)HyGZs?ALP)<(G9_(#T42=%@%}yL1 z)Q(}z>y!ri0b9FEXXj5{(LtYlz%CdZ)9=WdMymQ`P2+M>xdFprfR1cg-Zs64)25cB z(G&vaS0d(L!!@Zi^7pQ6NH;&zrZz=vLv@IJ@v=$WW-&C?jwe4o70Uk^ps0w}rvP;V zS_1z>MgtJb6F4twNmQ)#A-hr&B!3{k7eNZXgcLXukZ*fSXKlY-l?*X@O0zk~IKOSAKd=>);QaWWQKqlz;0KBRaz3`l_ThBH=^N^Wscp}fOO z^;~qddIgboxLYiKlX%uBiA}U5-z_fF6gTCw+j6rr#v-#L*1QKfxwYJCiM&;f-R=%s zz~wWw)aWEF9rEt=Gmq1*QWjx8%*x7;OgbJEn)MMZjJMjYCSrG5Jtm9Y=CVgeU_g|% zn}@Vk4SC1$G|`&y8a(tj81jqd#M;^O&9%s3f5ogyG(5gFQ_*7+Rn*StmBKO{FO2m{ zfQg@K;~d}Er?4js2ow?pStn`@8@l>Ucz)lN z4AN7;7hVe;AW272NGmRy{Jjmus$ao->4cX;nJ}BbN=s=>{UfRh2$I&g=XS{nIb97( zYZMwoXLFOg=BBSG#o`!Gyv{DW@y0!KPR6Z&n}Mj++H1G&rrpu1xo`AmFjW`1fiHaR?@DpKOy|KPAF+Uy+46Nc5>70&sJ}8F$_sK67CO1!?@3ahB`Aa%tpK$NT ztF_DL#Wkk#$sN$9lRv4HbZTUp^#j=Q+Wjb|leX1plZ|e*`6E`yt-{`v6W;u|nEZqaY~YNR!XO^tp-#-YtpSKWv(YGv-0HP7oseYXGOX4z|M!;IOK{Pc}GHR-QqwJHq# zz!(h2E)%oBx6e+E5v+fC^>TY57V5wMASJV+I49-)>1eJ!F}|`mJrAH)Y$hHX86_c# z-k{Jkq%#x*fI|2x^7Bed5bgvvQ@=e#M<1;gR+@A<1sq0e z=c1w~N%D&t@*gndY%yt2W5oxCLoUCQm<^7Gdad_(E#h_e$P3FkEFP;UgFWfuwi|ow z<~~&OHj~W`Jci!j@&v;1xuun5C{k=6#fGsCBeITc5G`)lkX|%c4VnCMKE8DNJo&>q zk9oL5(B}-qjAJpSZBwjLV=VXO>!OW_o>ElMRL=oo=+a-pnQ zDuqe)UMS_6hE}@c_p|kSd98iL>1Y-tfB16bEqns1L{0A&I>4onZg82=-g^n&5+1B8 zM}zivWx{F}yO0rbh%_xmi_hcs6B}Bi9%Db24>_EWx1OC}B0EPrXMspzT4wk?IsP)?ZDYj%yq>Et81Y6iKp=6rGp+>Q%4t89&GGi-G+3U zgx&D;Hbh9Jj`$zwtXVdIq7)mDSod3ytaiK@7Kg8!jFZtPMa z=&PXvLe~U;cIAP8zob6+CrmJbv?qVAuHaTM$~#8DWyc!Q)nJ`Os+@5N#e)ozo?jwr ztJB*VYVnO*iHVEDdJFURz08852low4eGadGV03(BXmVc9d;-llCg)1kddtNBxf(Cy&KBEmKSP%;tN{ZQP zlf&QH?=kl%d1c(Hnc=SPrJ7PPzu7c6G&dwSDzZzaJlmuEw)~=eMhO>iin3ZP71j0_ zdvsQd-RVNt%sudM?0Cm#)3>v$$~efrOR{r)z1_NoUYpy2?yJodF!b7tI*Z3PJiCU~ zGIN`djz7N~7<7!R!U5>sP#R2W6@Xw0ZLv2`jt#NFR+Cm^>SlWGDEe8zXa(r z$?xhgnLRCyOSC^lFGCtMX(*-c_!aC&gX{J1i6SkiLMj!kpo3q`<&Nv?WX+vperKEf z6*;^*7%=z9_u+q-Vvr)VOd)~mI-d^5{yvug|UXQvQ|_IpDC_wdq{3BN}7k@mR1PX1(J>~!t%THj#1s@Y}= zby#9`Q>!I?_$FxH|MNH`(}{Gutw%f3*gP{2VLF=5z-Cn6AM*-=4cMq9C>G|bV8pA^ z4D}2036|5#Rn`%|yH$P!%pf!naTqj$QmO$}IiO2O$XgIK{u%xfttGBacwJA>4a4xm z(xNIBqoQeWaTmat^R=L)%+#doJJa2N`Qi*t$Pmo|efFma)=qdM&Pr#3kS`@mUg&#V1Ch z!EHDpmA|8GQ3~C;0@LHuQ@GVg&aJ*;qwoUAWEOVVoAn zwz*>a%bk;A;o~xfnn@TnnFUplxWwSK3E+OC0YrDR(cIZ>pBfm1v=HJQQJPoe zXi=-WDYL@l9ss~`#oyxx=a)%g*A z?@N5MP%A*00R8VcmNx&#o7nLsYw748?Q$p=&T;eolxFAnl~#e2G)c9p-X_?!jTfSD z?7yL!pdaWegi`Y*zs=4qVl#R5gRAJ!JUrX<@oTM30oDENihO)`CVHFf*zz*@ zX`42fa__$IZfbGqy|gmE+6H-$;K0aeqEP`xi>0qq?~1ZJyC+>~Dfd(IGK<6+?3-=g zAO;fiixjjznZT~Q*noE*AMFYqhdYpVXQ=;CP1~Oq=M`axZcUfd8v*~IkI$>`uM2mT zS2as@(vD%DzIk>jrh627^0GqFSIl7Yatj#6Sqy-nWR1<(P79)3F{=#Q&&s)is`fm- z)S`u8U11rcKu}zgr}oz?u>s4CLc(pwc*$%x=}h4S(imhjTRUgRC;Lz*ayeoL_)x!w z$>`%vGxu+jKU_(EG|C^&j}6pMu=-woFxry*|3${#IM; z5U2k`@S3j$U};)PYk4ZELnpy>;`=l7PU#pQ$|A}-bzQTgD=^wNCJ(=b-=k7>*AohQ z4JP4-Zv*s1-hOfM5FI<953dp!nv`DSN3U*bQc%QVDzszE2Zv{mFV@1+8Gc(-BF$yz zmr0AqHS*3TOQ|>(O8)v~r+FnUB{Lg%c}8(+UL7tKhc7q~o|q(llims;d-udbcz9?8 z;K7uPvfOHMdhdMkYr?!83eJ8aJ1+%)Mq_I#qRHt@<$kgw0L*vW?JfYrdpP1d4U7BCt z+=d+L-hM}~)7L+=iDwLiu`5H;2mZgv`(q0bDNi)(1wxliy;K(&@+pHj7W>}?GU3_pe+QQ0HvigfHm%R{OTOK(HEzYtwApmFFjouAJu@L^ju{jTZm z(Iz@0LCJxAoT%#YGw7PQ0;x_od5L9OR1M+kB24}bof&+xYe-LCZVA8+EkjGkXQz)I zuDPdqqS1EE1J=!3!bWa5v%9M)wUYv#i>=>r>LFXfwj!Gr`;2rSy z;eHSc;uMuIm_o7MIASt7dfnDBPEAn&m#L>-DR1nG3?w$@ZT-5+a_mtq$}cIXDiq~6j%(|!c9YW>v^Z>j zcfZM{Z}5$7uYe-}mB@pm)vdL512OfDt)UA-H~d}oJaAxNOpotgJXKnBSlrz-KGNlA z4@KxKk7CCtdGa5?R<$;?XeV$jN}F3;eoJq_-cwS>Vi#xS7PG6_3br)j*IJRTz~6$zPCO=J z)VMd+*A^~!k9W|5h|Ou*vDIp29{2EdmVez6{T;+Dy~)qE4^%OQJkX#&b1#fHYo_<6 zj$gqPUt`m_wq4$-F>0-Ss?eZq6@YrWt(CePwptJ<<>S{MUh$l1I0mlCSSwPt*}DuA zgHz^McPBy*ozT-QK~xusGEZpn00h~~7tfv?o}%CKzes?;y^5IM!pYEgA>U?Z7v$$O z)`n*HA6`7VSoVv#+iLxTzIz$%>4L{~F$aS^*l!!1+}X>|&dSM3$<8XyFJ-2Q#3fSF z29R_hI50jI4tPBltI@eSK9O_pMp_Q$H!^M`qc*4TU@fsQw>UR5y)>J^w(Id>w+-JF zm~K>l1{z)zvPwJIFddUoP@G>~)oOADg8i;;DOa?o=;pha{1D%o{E_A9TtmlxZ**;< zYY>oMRzYcA31M?f8R8l?qk>yqFH>tejC~!Ab+S5)TEk950lTP_%`IkBl!}UxQ_qHU z#O$+Lh}G+|_uHKI?$+?$#>(E#!3B`DGmES9l*#Bm-Nh&RXQqPG;G5)+XCluwg6*S? zT$fJpIMFgkd_!9!<47g`_bo}iyuGEqqouX0+tzFL>&CVpfysq$jjCo-c``aLqV>Xc zril*rTLUtI95@gC(CAqIu)ZEHiBUX2)tKAT`kV?lCSN{#@@QulL-&9DGr#-?z2FeB zY3maiX9jtY#?)zp@eOkR@O&#!$DD4K78oDAredW(werI125+B1H=TUPnVOZ7`yjnA z<1#P5xTrd(Lax=BJYN6s$lwqeoC5THIWg|6PQR5wigNS#5;=?iAa#Fz0@56_b5nD; z+a}0x*lEEO@_I)HMT&fmB;{I~5+uV?RzW^PSWk3%pTpa4uNCl?TAFG{I4n=2GWn~C zmYtC8k##6AZENK47)1q4E~~txw1{0&&ManCR$xtGBVt0gPOEo$NXXc$;vuu-loXY+ z1ma?De(|`w$K=3-!XCh$Mknd->1-W2+}t=n+Fx1TT3_ATU1`9o+1TC*BHz!i;v;1` zP)UL>S0bBRrs07)QAn)XH;%4NI=jf33s+c^LXAF)vb{~+)TJl7&Yp;WQRO(w6j)8KH|Mf%6g0&JX+)i!oogP{93)fMGtmDFr! z*ElPc$&aj)FBW|I8EB<-k!n)TV3jiqiVLCfQBqpW$Y7TX8n{wZ2>$T}!BCjy8c$satJVC6*G_v*bq|E9Q0?!M5=FE?G~LT5s*>w)lx< z`V8k!zcr+ga5zfkq}|Tn;t`-R@_mUBO^{w?k9ux6G(g7c@pV6ii+)7t&P&>N0-)>J z#re_NA>JHjqnXGhHQjY}_ZR&7myFz$ba{JQB))x)6;&HPX+>*8Ub?8@fvkW%$tn&f zTn&>`oGx~5TIPeo{IvYr#i?m|1-W@8ocye+20Yr)iTJ|O-p1b6?q+=G79%q)i*Y|U zt(ji|RPw#!rKxprhNeh-7A3*hK-lTMx;ZG7mwsc{s$17I3N=fuK7iL7Aqm%G zYaefG4esFNsod;mFhA{Q()lgzNYfonUp$s6u$0Q%-qE}=93GBMDiMDuCJ_A-m>WN^ ztNyJQxUcq>U%|hb;T2Py1kV`O^g1c0&CFuzn;koTZ1FTEu7 zyW_g@F5N(%X-Lw=%_He)g{3KFB;$Tc4#_Cu<_gm9FqOpY4vbC4SGG>Kw&&wLcQcFf zvqdB~y`zR#Emzz>TA!Ypotr_BoShyY9f|s_6EGDWOVaR2Ct^rUaF;;M;?-^+I+F}e zwM5-x0e{5PE$5agDk^yqAfXF-`y8Xn4OyAP!UOrxquaIrB zf<*}<&a(POnY3$#E^`0n)y_4Vk)Vnjb%6yl($k@7;tRGHTHB}m^JB;B$K!l?gWNe> z8&bBb+FClyeTFV~z+@%fDeNVn+l#KLU$kJOTYKK!5G&=R(L~yHbCUy+>4`=--VYz1 zKYB@(FyDRaCDarTc2;mA|3CurKd=`4L4Ax;{XVuR#P?F~-OVB@|NQpB;rZEyucB-z zaHok?oS#STZ4Q@k?5&fImGEfmy;PFH5#5obW~XPTXBIPAg$PY0>>`o6(>^?&SXrN) z9t$W^vN8*^*m>Cn>P8`7Rrla@inxO#BolckDov9P?lv<3`PMaS&OjV;jn>*?(2G)*l~#fGaTS1>nibkUv(-7rmI*7)z2&RUs5X=u`TXU0cE za|4|?gyiYo{^c0~Uggb47l(W6D|pvyL;ab$7c$1*B|ixgQL808^UmEYh0ry%B@t2Ypd%k3iiSd-1x*Fj7*M?4Gs7m7&5?E*XSBrS_8`e zbdLA>-HamS5zO2QPD2lI^f+BQkv923?H*TMTUT3ECOVI+yPE^c(es6-mmWMLHPsLv z#!{8y0#*U1ppaEs*WS@-f#e+K7ETh4uPoMsm|0d+%B>Jp2&zSFPF&L@(>ChNdMkX& zV(cY`k#+bxKs7@HW_B(#V!GQ@islfA5O|g-%=~)X`a9$Wzjid*gdL`~W{@}i3+qja z#nn)ROzIy+1iiKSgPbBIiWUdfcSMMVH8lnk2umvpDyb#?Zi37JY; zr|uf}LjfKefKQPxf$)7C{rC}0$s1%aQtMJA) zu~gz@A^NH;y&{EKR*+Yfo6_c=$j^%|LFv&}G1HZq!)0WOQtoA?J;=%-C0RLAb$^f` zB^wxxC#GXlt8|SJ9d^HKVHOrWC@NNzqxO(+Cq2Y#v>Eih$SU2DaL8`L*FXyks-EBr zzO|>ipH?j8^GZsY7-Bbf_WR9lo6bEb;EH)fTu}wL@RB{L^7K_A7)#WT$9S9y!l^1@ zmKH#^4^uaGg+S3pRGJ>0!P<*)8=-qO4+d+iq3T!;jaMR;h`Q9qUM!kuR5$B;dL4F` zp?7j&JO=1mWYALuAZ~e8+-GRkIgvzM3$D-&(0_r&ei3!=i7UkJmbz}Qop0r)g-gl3Z#|^{rk^Yxs zW)TY@Kt7cE2(uuUgWOPB1H!JlyHDE;b#N@G1{-N*jqFE?!KvgDI2T7q+WR`2`!rh2 zyHt8ri(Jz*G#Z73`plH8tO%_We)Y~)+!3<2FTwNw3&x_Z^;ewl=VO~zQln7C%R5Ky z)sl^g;9MW5zQbdyn&tLDvt(GQuGOmK?K*E)bNKYlD|^wN03{2dXN4t_G}WLucS`D^ zsykSrqbqA3^Yw>(-WUqfm*lO-FVD!q>dMsU%zk1J1v2$0dRKu8HmW8O|I|c0?!;}%F{!r8-_z;In#^Tq-W1$RyOoofl3!ev zlU~-|=XBsYiVj6b#^Q53&*;iP!16F1`n2*pS#{Wu&aGBWM#e^mNyuaAZPzp_)O}3? z2xsBjp*VXiM%k032`YJVNnO}1Ew3ud;i?=#$fz3|CH2fg!r&zeh5iOc)x&x3VbAc) zL40t9JHjqy7jg1|b}uhwmses#x(A2EyB|Z9XyJKVm!d6?`ScoCYFAxG4$dRf$ zfVVUlu^hOuzD^;9zLfw$`Ty(bJG>gpuQdOW^|9OCRYes!iKK{-gmTU~=L`}EB!WOF z3q;NtiYh_@m1FBwx2Nsi_5^!oZ1;@YlaB3~@!H;T8qe-Idwm$g5Y*GV@7~`H z-~GM~08K||c#H)qSj^-5$6IqrGg_krv=+b{QR4O87xzUn4s*N2DGrWnjl+&rZ8%n% zUEV8428vOwa9Zb3xx8A;5E(XS5sM$7!&IE(D_XyvNHoMR&2{IMhANUpYnEqqV zXdM~OkGRj_<?*8lB9R$yElU6{;SeXJBZ2Di7QcTRRVq&Yu(r3{F3T+U*^L zWl3)|J`!?!jTWgvDH5bs${;A|BDKTl74V*%z5C}UTQllqFc@Mtsh(JX)vNr=q)RT~ zxVSN|$2?QwE*adW;hDAaF8h_>iABWA+AJcI-2}(si6MtpxKN`JMS2K>pa54D5JLk} z#9M@xfuLCly^5Pax1}ElC!!jNGYFjrx%<1fFF}X$;`m@2Ho@CExP$=SsL5E1v0>u=p5-I0=Lc&Gbzwu?qqxF<*FKRmv-($a93 z-`dsOLTJX?NaiNFKRA^b$)$? z@X3Uc!Qpr;9L0tLNRi4WQ>ZobTi8w}$tN;6Y>u2KX0D%N4^J12%V4IT{%I2fpsvuY zrm1Z9G5&zer4^e*Nq)dNIytw#xgM%~Z1zYMBQ_26?j4565V9>~Je_i=>ySGoLSGmRwLc>5|d3J=?)$0f(hob)QfV1*B zo=oMasp5VKi-L(FG?#TeHNWw2gMZ28^zuYpjLD(P`}k5tKh3V!8Qe~f*Jk$srv@y| z6`+FHJUV#x@{N<&13VBjfd~vmeX%Gu==b@Zn36ZRy|%oOc9Ix8F-IEXa-`vn)8{XD zjf-%{ky0rFx}l2a?+@&lz$BBq#llROt>G(2g+5QIys~$6;4(s^pBr_U#JYrDGPP3q zY}$N?rk_=9RK%fTD?rgf-!fm?4)Wj)`I_m`JTconNLTLf?)?j}4^d*2? zk@?VA8m^jLLm}Nd&ts+e+0tyOn3)+Hk53gx-@*Msw)?AhBMhC`XdZ9@v6gUZd41Z~ zMK#2L9zE((C5Yr6N@p*4;Ug9&x&d7Xll5%{NkBaU=2cedKMQ~CRT zK`>#IDkqKVK^O-v%XYx)5LF6-8=$=YM#30i2&fiAy$*EFA>tdDi^B}%x0SyWj60=p z$3Q0rPK!eQm3#ySCdfB)MY)QJslQ zE?>x(R@b+856(_uhaMi6PvN>zO87T$g`eN@;BIXc7huX+%%|bkbbKgTEJ58<)eig1 zGmczmFa;e7ox_Ru&hN}dJ85By=I&K-W&gK^G&yi0m z--CW4q}*b9Bs`W)503Ahnzh@NpYR0nDYFzL8T6h9Z+8txa8#g-5lP7dLl`X1AMbw)ZV7@+ZtLa z4J|lBKvic7PQVJ&3`ixG=T_F?ggd>wf)#AUQ-@M{bSi(<-B?$R z;n~x-@EBEX_E}b8klFlRozm`hwzHQG7vtSM7O0*Qv9M~2P4A}j`boXQc^(r-oEZtg z;ASZC;*`ygRlcBgQD_u0+X<2h@i8bwmS@-QV~+tM{i>%6 z>j=4qJWqX7qwY{rJ(+ymY#PF{Q;NtJ>gEn9{FboN5v366u2@*d%Hx zqKyIMcF1-JzlA0-{|q<-#BExI?;!4?-&cMtm7C$EH=Aj27a_xY`{e4`_1PnEgntG5 z{-N?;?|)zUmy_N7lhZd3R<<9mZ{2OpK0HX?X5{Z*W0y~ka*4Yg_1!%U9rY^HH(8Ch zI`ExbDW)(kha>f>=ih9Q>f73zdhXS8Tt59h6Of|KWOCWUY#GkERgfFIc=`;1AQ8#| zbvh7{!RH@-)Vft%SS}ami%<$o#$%H&;0#CVj%XhESC66MHMs{ZYOP=I>89rnmPdN< zhM|dYJmNBrP`OlAKb6Ds4G|=LJt7&?7L3NH9!;0~4DY(=Tm_j)VfA#;I0|Zq98(!I za9>^Cu*VM^0m}FG97Uti zpx}M?8a`IuzI=6sojii?`%kbOay?WJ{P19R=h3U@8*2~u*B8D~H*@?j^$k+y=*iXN zr$_mT+FMlIy_#F~m&}W_)xwc5-vXA#QlILH1cmtI^xS zgP6)~G3asR>7&h&`YxS+G@6XMY-51WVc@Y&(b7OKU%{qx8KS|0$+>-=&F}KbT?_(+ z+J`YHG+@T;k||8U0;CNZ>;Z?zJ)9U#r}E|Hb)aNJEaTXdmR6#37>FFgfiP5hVXHZ^ zzB9M6yE>s@f=n?8*>co!g^nRzhr#8UItEvFgU5$EjON2%p2@5{iCQaK@L1X6h>2sB zxKt~)(T&UX{8@x=1wL56%c2L)FNiFkoew_*u?ggPXi*iFabH2~L_HGZeN_qBs{cVr z_x_jI@6PC_(0fG_v>RmW$4{@W&rY9!eSZ(OtJZS=35O$gx^sB-;uzaLT3bwi{Z{Pw zEPJ1<{uKb@c5T-w;&J-)bpj&La7hRZ-1CWzgJ|A#xby$I&l$#$(a9Hx@%j zA*eDu9@H!J9=};*90><3$RsZgqx zMftNsD!B#HO>s(WnqN4~EFGRE7D8&xpxtMR01pwWin23ENdc7w$jcx}`2;j4!S{Xw z@v@5YK_%V40mK7!OnzJWM=Cs8K%5VE4(#^vvnSX0VP*Z7;B6?H{tI0%7EQqa{Kf;hfS*xwh^ZfcBi|0RiTmLKqTau67&Lh1j{N; zS$j$ScnYg8%22QE??a0LOPG#;_~_+M}g5oZWXqo zZ>r47vo@PGG7Ou-cHdwysh^&fw9<9z5r8*c~XX8;brApS0McG*EgPq z29~HR_2ttcxkjd!2~|^rVlS-yOJzk1XJU0_Q7YqTT!-g72AkJDc?5489@y*(cn+F; zqJW7&$>`vK78jVVI#H_{@GF>NqaTl48lec#Lc*_TSOxp^&FdFHHGT02WcNM;+o4E_ zJnxV2B>hhShCO`r_`$~J{>pl$@U>fk&xl+sz)7f-^F1ilIje(+8Kt@JWJ;xah=dbP%>G0Sz$_~_AbvV$l` zP3pMYlu!_u1bQEZ;Z+iq3<{srzaVE8Hn!(_n!39P`SWrYY!XnJFozUy1!fykC$?f5 zqtg-?8jU3~lZBPF!*gg8z!_XQ!)ZMJ(BN<=820%CK9i=nnp!xUi;87Du0%}dh_oVw zOvT`5o>xA-jD+FZQNTn52`~aZblYTVmDD118FUi6L==$wdhL@~KDQ<1du858@W^R) zE&w8sQlUMCp!<%%F8Fa*g~QF^&0E$#R{z_iHHQR4yL`G z_{N5odO{swcpG}_YWnNwACL{PNMO40Hm;t`A;mjdy2S3-*m#?D0<>3(*&;9i<u2s~G~CgbY(nS&w#l2PnrFU3emV&P2lZVl0_S zMbMfs7+qPNn9glB*g^x?=wvaKUR-_f@afBE?_NFY5(Qx`#}^p%4G(zyUe`o1HMd!? z$&sKC596qfO0mks<&9swxs1l)0#R@Iruc$8{G-bIh(*pDIRoaf(}d{~+b;l7TrnNPDULD~l${_vD{84A=0$YMq5nsptJV$G<(=v%2T@)j zMirzD^vT=TZ*H~--@LxMfS`{WiYR*j4m|KTl^?x*d9{0gBY%9bw)lSjgKsKVPF5Mj z!^^?8w41>^cx6RsKN9}5AtdZ9fq&QQLEi}~^^45RYizBu*x73mqo}r0It8FS( zD$JI0*;y=ID9x9b*0zuLPp{#S0sA2Xp>Zy(jGZN6Z2+5Fn9UcaQ`mI61TR5;`JMTY z->k8zcvg!}tuh)qh?>>y0+XZzF~nrX<{lOCdTD|njVkAoD5N2xEGSNK=JyXBG zaocsO!0`V1v_>lE=Sa8=F(wy)`Y4|sI;(tqogRgYMjC~{cmi$#!|ckp^D1f7EW+eg z%a|6Q_ zos7wN!O2Vxn=N5^=%|#6b4$zE`p*6%>D1Rytb)a7T>I7s`YZPCuV9QSFC(%a~AyK}KjDp_;hr549x;PtJwjrWAO`Vj~I!@eFHbELWF4hZWu zb%=eD_)v#@GCeb!E6i3ql$S)uvdyKc~P8{bR7`6gE$~V8+&mXM$Pn3_PXZ&fx6N z%gJ)djB$?G2SfJmb`pav)Z)Mjx23g2L|`h$bPL^3s92EhYxpx5tSzmPKe z8PF9O3it*CKATRanAz7A+stS;x|;*|~Cd7T}SPac1Y=uWjw0JiJ7VLm%{+To#o>r`FlE9xMWi_fv`D z%I3+lr||Gr-bR@Uaxa-Hl$dP6c*v=Dl#j;b0y0&=q$wn7xyGhtbn$395G#MTAA?(l zG6+_$i*qu^2CeZ|!p&Un1=2K}I&LxEL zM<*Rxy+O6zKY`z&zoU!=)eKc{tnPMzPeQu+-%AFliv!q^%*yKg%=9SKpqaS?NJB(`<60&f0R zb6d+D?c^cVJ_Q`JAVPS%rnVcjh{vNJi4y75OtCNvJE&=hnX|>&`Q_F1-GloVkgFh# zFIY_k6y)DlzTj%K3IPik&nb%G{P`ME68Pbx5RU+2$tIr^_I?fWo{p|2W@&fD+avNN z0Khn42%AY97KI9Q8w{?H2?}*&YH91OHnoBg$o;tPmd*|WnXfVMa5N2OF}qDbsbtXU zl~$kK8SsVzF)UeF+&eyhcJ=o8yBGbRaQb1WN8yRhVSQ+IMZ@eRQs`8bSs)P^baITW z_qk88KRb$o|NI2zG6=8mi^^Xdc{ySkriFHk+G*3dL<)nJpE}MZ#s&mdwOt}sIMPqy zjY7wL#Qq3^4DzTS!EuHTIAl4LouF9xcK{_v{Cp5E-=nS=+JZyl)+$NVFTt^XGrM}Y zyOc==tSadceAh4Gwf_tIBXBQYUr`9MvS{AXT zn?{*C*%`)5qOr)(K+rtICH9bM3@%I3FJw~1B(ex&%~n27xJL9ngl++`t)r8~=3&xC zqEe+dIDi(}>2vGNN=ywl^Lu^KSaL42^x*Uw7-g}y7gV-NDyGpXo$Wa0_@p*o$cQOD zfI}BMcuGv{R56M8g_-OT-1{#c41v}D37icvN})sU<7Y9E+^ztSoY}3@J4GCsAuMK2 zZOvwP0j!RRq^iOy1T^rsf_w|!9Ga)zB$f~)ffpJKgo0&tUWdXYG%3IwH|_?d0*nH0 z2#e709UXSR+g?35+FqC+8_@G7;P1bO@AwaJwVy9|M>470;aWPAU)f8?LT-&flfHhI z>kV&%6y)o3{|x(96Q!x9r2|jv?&xj2UE9vNTjQP&o8nkg?d@AlFc1w5M5%WaAX*F6 z98BrvbF*0tc+j%N^1{mc_TkB6Xab^$59cb{egem`>fC<2Udy00-swYkXBFP(_n!*d z$((+n(=5OwwgDxRjFYhqiTlT6l>T8XJ~9@y{H6@9l+jIOXo<|pmuty1 zjnt0o#7H10FBRarxJtR&YQwZnn_pwoyBr#`&SrH7hewAYo!vdVeg%Y^&lOy?MkSTe zxLt(4+=P0#6eM*Jsr>@IoR6^#19Da$5u06l0M*~OF2>>GPr!62vHUd&(xm6_ zj=3qc12SNB6dMab-^({5t@FPG(ZVH>ltm~lhg_v9`cLVsuY;@XOR0h4(c!zCx(d#Fw0W*Y*`e4J0>?K-Fj)AAwnsS*ii@C-5gS z8#2|G;B-)jgZU6L532B+ToUz0Q7-wbe0hCqd2Okunv(j}OkQ zY_FGx17S}uW=kAiuPB3SXIGD(J&4$A8wj|b+E#jF51~mWsHv0PGqf^!OE?-(&@|lZ z6}H^QNzAYXot#MJVSJK>dJVWO3VCQ{xAhRZ+K6O^ST2R4NvDQN%xbfM!n4V)0DsfEJ)zNLEVsCKdiETI zr7Dk@0wt!=VM>6FMbnagI+fVV*1_bF!c|x;1_r*5Zc3!qfIR-m26!0S7DP=bgs%VY z7*8xQ`ZWf%Q9dXdp9?dS+`yuKX>wK~lxcv6#8Wr`*<>j8P=gjVh)`sNW)~O-B_Y*R zAI>r`6D;Jz&j!448CXBPdh+Z+fp@3287NnA&8>~9M(rT$uJJ=q@EbSzTkh3EJiOBZ z8Wq9l@OWZ$G6f4zu+&?~mltP?0N*bFDbVWH_CEACfdl!5R|YN{dj2U9h-w2`alqpn z8IlU5Y60D7*Rhzr=0M1-Vi1T;-D*n$+nds}ToI50_xNQPnFO^jy`R}1W{j70KDXUsG^q_vi_<>_%7Uep!~KWXudt6_ z53|%#mD!{*C}eU8o52*&X)2?H&h6Lvf@T8;qx4vui4q(_FLs~8jYU};3Qa$&d?%{p zNM(MvX-cZi2ppz4ZXrh=klI(nPL4`s99TMf0cRcPSi?S7wTFjlG?Z3UbHl1x{?rP& zIl7Rap|2s|`a5JUFc!)it9#d9P3FsMyPF4R7cWlluk9W!Wl9^RiJ3$OJKiqGgLbE} z8!zx4KHdxYSI-|mdHyiPY`)#p*vD=+H?+9xiG)av*ZMVP17lc3{ix%1T^CQ`h4t&$ zD5yS9CO?4@K&}Ymw7E}8SF{PMT@wI*B0GZ66wU?gv~$XH^J zmMvn5Vk15)jR5|K1Z#Rz+x;keT`bPMsF~>y>^qwYPMLNE=vHGWRi0;i@Qf>PhP!)G*O~Z=~Y&p z7*hyk5kuNR? zz2Qx@V_9b3>+@%{0=Wf}j5m-FBd8>b_W+lHqYI)w26Gy)1?2aiRMQI7Ge*{_#>bl` z7WU6zyDHBGg8Ly*<-d=lSJpRoE*?LB1&E+Z<$QH*DGlO=lN%>{tEr&PWsA2pll3oA zEin;VdvyKe#i5YYdatI9(n%uUt@Sn1sd*w}vI$@QrqI!GtFft>V-F7mBLHk29ZODN zAox{CmvR8WgfIvMz6)hovfkJ`IlH_@2_k}`AuJ$FXUIy2+ZBk0LjysleaNLy>t$3H zon?-C+y3!sChjVqpzU{wkHH7rjD|VKEuhVhPM; z~3L*6x^%dE{`mRpSJw<7Tc@!@Q&K4w z$^~Mb7G^t4y3!pss7ZKS7qeeuPz^y|^kew_*-J1W!ZxA?$j_&N8H20x*$4FYltvJd z4on!!DrVBo9}MR_uz~arOol=V%7(uST~-9RKxyLtkB2w81X&;zOB6ksdhRkoa8-oJn!iTYMZ?gccX_ZQP6nfy%Y!PUcJ$f(l~ET3Y{bwcRw z!Z`eRXzc;WOq|O*Z#C33_mMcAcxS`y{#ur{d$i>?Xhw+H=1!r)0)U%1u>OosOv1(t zFaQ8BArCQfZniuNc(-|MX?ttuc>m<=*_)e4kGu}nT(g)~ZuJdLjRM*-6E-M?ER9mA z4o{Bh>CH{`bd9CE6K?@q^`}!Qs>}%pW!nFV3+yFOxbZSEey)iI|g Date: Thu, 17 Jul 2014 01:20:19 +0300 Subject: [PATCH 380/488] Tests for DcxImagePlugin.dcx --- Tests/test_file_dcx.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Tests/test_file_dcx.py diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py new file mode 100644 index 000000000..9a6183651 --- /dev/null +++ b/Tests/test_file_dcx.py @@ -0,0 +1,45 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image, DcxImagePlugin + +# Created with ImageMagick: convert lena.ppm lena.dcx +TEST_FILE = "Tests/images/lena.dcx" + + +class TestFileDcx(PillowTestCase): + + def test_sanity(self): + # Arrange + + # Act + im = Image.open(TEST_FILE) + + # Assert + self.assertEqual(im.size, (128, 128)) + self.assertIsInstance(im, DcxImagePlugin.DcxImageFile) + orig = lena() + self.assert_image_equal(im, orig) + + def test_tell(self): + # Arrange + im = Image.open(TEST_FILE) + + # Act + frame = im.tell() + + # Assert + self.assertEqual(frame, 0) + + def test_seek_too_far(self): + # Arrange + im = Image.open(TEST_FILE) + frame = 999 # too big on purpose + + # Act / Assert + self.assertRaises(EOFError, lambda: im.seek(frame)) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 3c8f858aadbad93dc02aae611fd96e0ce8ecf02f Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 01:21:56 +0300 Subject: [PATCH 381/488] flake8 --- PIL/DcxImagePlugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PIL/DcxImagePlugin.py b/PIL/DcxImagePlugin.py index 631875e68..0940b3935 100644 --- a/PIL/DcxImagePlugin.py +++ b/PIL/DcxImagePlugin.py @@ -27,13 +27,15 @@ from PIL import Image, _binary from PIL.PcxImagePlugin import PcxImageFile -MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? +MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then? i32 = _binary.i32le + def _accept(prefix): return i32(prefix) == MAGIC + ## # Image plugin for the Intel DCX format. From 665293e9b9ddf19264f55e26498fc7092d9c96be Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 02:38:57 +0300 Subject: [PATCH 382/488] More tests for XpmImagePlugin.py --- Tests/test_file_xpm.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index d79f5fbda..7eef04676 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -16,6 +16,17 @@ class TestFileXpm(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "XPM") + def test_load_read(self): + # Arrange + im = Image.open(file) + dummy_bytes = 1 + + # Act + data = im.load_read(dummy_bytes) + + # Assert + self.assertEqual(len(data), 16384) + if __name__ == '__main__': unittest.main() From 8db043b35f7d587d4a5db23e72db3a335dc88eae Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 02:40:14 +0300 Subject: [PATCH 383/488] flake8 --- PIL/XpmImagePlugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PIL/XpmImagePlugin.py b/PIL/XpmImagePlugin.py index 701a23b64..517580895 100644 --- a/PIL/XpmImagePlugin.py +++ b/PIL/XpmImagePlugin.py @@ -29,6 +29,7 @@ xpm_head = re.compile(b"\"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)") def _accept(prefix): return prefix[:9] == b"/* XPM */" + ## # Image plugin for X11 pixel maps. @@ -86,9 +87,9 @@ class XpmImageFile(ImageFile.ImageFile): elif rgb[0:1] == b"#": # FIXME: handle colour names (see ImagePalette.py) rgb = int(rgb[1:], 16) - palette[c] = o8((rgb >> 16) & 255) +\ - o8((rgb >> 8) & 255) +\ - o8(rgb & 255) + palette[c] = (o8((rgb >> 16) & 255) + + o8((rgb >> 8) & 255) + + o8(rgb & 255)) else: # unknown colour raise ValueError("cannot read this XPM file") From 1e24dcd3170193ff3485f6053a217cb000f27af3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 10:42:43 +0300 Subject: [PATCH 384/488] Check some pixel colors to ensure image is loaded properly --- Tests/test_file_cur.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 5dc096968..54bfe84fe 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -15,6 +15,10 @@ class TestFileCur(PillowTestCase): # Assert self.assertEqual(im.size, (32, 32)) self.assertIsInstance(im, CurImagePlugin.CurImageFile) + # Check some pixel colors to ensure image is loaded properly + self.assertEqual(im.getpixel((10, 1)), (0, 0, 0)) + self.assertEqual(im.getpixel((11, 1)), (253, 254, 254)) + self.assertEqual(im.getpixel((16, 16)), (84, 87, 86)) if __name__ == '__main__': From 5857053be40a3f0f8dd9802497b8c037b9b38821 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Thu, 17 Jul 2014 12:30:45 -0400 Subject: [PATCH 385/488] Activated MPO detection. Added MPO to the list to be checked. Removed the superfluous second os import. Added OS X & Komodo work files to .gitignore. --- .gitignore | 6 ++++++ PIL/Image.py | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a0ba1b4c1..95ed4bac5 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,9 @@ docs/_build/ \#*# .#* +#Komodo +*.komodoproject + +#OS +.DS_Store + diff --git a/PIL/Image.py b/PIL/Image.py index 2aa2a1f31..b4aa11e20 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -352,6 +352,10 @@ def preinit(): from PIL import JpegImagePlugin except ImportError: pass + try: + from PIL import MpoImagePlugin + except ImportError: + pass try: from PIL import PpmImagePlugin except ImportError: @@ -554,7 +558,6 @@ class Image: self.readonly = 0 def _dump(self, file=None, format=None): - import os import tempfile suffix = '' if format: @@ -2228,6 +2231,7 @@ def open(fp, mode="r"): for i in ID: try: + print(ID) factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) From cf8f191c54aaec0d39938edbb12f3fd37b16aef6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 17 Jul 2014 09:51:44 -0700 Subject: [PATCH 386/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 21166f1b4..4ea730cf6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ -- More tests for SpiderImagePlugin.py +- More tests for SpiderImagePlugin, CurImagePlugin, DcxImagePlugin [hugovk] - Doc cleanup From 14d1ac6389fc47f9c44210991cce990f68d0b185 Mon Sep 17 00:00:00 2001 From: hugovk Date: Thu, 17 Jul 2014 23:23:50 +0300 Subject: [PATCH 387/488] Rename bad variable name 'file' to 'TEST_FILE', remove unused 'data' variable --- Tests/test_file_xpm.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 7eef04676..16a811a4c 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -3,14 +3,13 @@ from helper import unittest, PillowTestCase from PIL import Image # sample ppm stream -file = "Tests/images/lena.xpm" -data = open(file, "rb").read() +TEST_FILE = "Tests/images/lena.xpm" class TestFileXpm(PillowTestCase): def test_sanity(self): - im = Image.open(file) + im = Image.open(TEST_FILE) im.load() self.assertEqual(im.mode, "P") self.assertEqual(im.size, (128, 128)) @@ -18,7 +17,7 @@ class TestFileXpm(PillowTestCase): def test_load_read(self): # Arrange - im = Image.open(file) + im = Image.open(TEST_FILE) dummy_bytes = 1 # Act From d30eb007ef26dff16d788b2070737f3bb0f2c2b3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 18 Jul 2014 10:40:08 -0700 Subject: [PATCH 388/488] Fix scrambled XPM image, don't mmap when load_read/load_seek is defined. Fixes #806 --- PIL/ImageFile.py | 34 +++++++++++++++++++--------------- Tests/test_file_xpm.py | 5 ++++- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/PIL/ImageFile.py b/PIL/ImageFile.py index adb27f2bd..5e4745d76 100644 --- a/PIL/ImageFile.py +++ b/PIL/ImageFile.py @@ -133,11 +133,27 @@ class ImageFile(Image.Image): return pixel self.map = None - + use_mmap = self.filename and len(self.tile) == 1 + # As of pypy 2.1.0, memory mapping was failing here. + use_mmap = use_mmap and not hasattr(sys, 'pypy_version_info') + readonly = 0 - if self.filename and len(self.tile) == 1 and not hasattr(sys, 'pypy_version_info'): - # As of pypy 2.1.0, memory mapping was failing here. + # look for read/seek overrides + try: + read = self.load_read + # don't use mmap if there are custom read/seek functions + use_mmap = False + except AttributeError: + read = self.fp.read + + try: + seek = self.load_seek + use_mmap = False + except AttributeError: + seek = self.fp.seek + + if use_mmap: # try memory mapping d, e, o, a = self.tile[0] if d == "raw" and a[0] == self.mode and a[0] in Image._MAPMODES: @@ -165,19 +181,7 @@ class ImageFile(Image.Image): self.load_prepare() - # look for read/seek overrides - try: - read = self.load_read - except AttributeError: - read = self.fp.read - - try: - seek = self.load_seek - except AttributeError: - seek = self.fp.seek - if not self.map: - # sort tiles in file order self.tile.sort(key=_tilesort) diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 16a811a4c..4fc393808 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, lena from PIL import Image @@ -15,6 +15,9 @@ class TestFileXpm(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "XPM") + #large error due to quantization->44 colors. + self.assert_image_similar(im.convert('RGB'), lena('RGB'), 60) + def test_load_read(self): # Arrange im = Image.open(TEST_FILE) From 31f8da78b7395b64bee8077a81a6d30b3084cf2c Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 19 Jul 2014 00:31:43 +0300 Subject: [PATCH 389/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4ea730cf6..da7ffcb84 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,8 +4,8 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ -- More tests for SpiderImagePlugin, CurImagePlugin, DcxImagePlugin - [hugovk] +- Fix Scrambled XPM #808 + [wiredfool] - Doc cleanup [wiredfool] @@ -16,7 +16,7 @@ Changelog (Pillow) - Added docs for ExifTags [Wintermute3] -- More tests for ImageFont, ImageMath, and _util +- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, SpiderImagePlugin, XpmImagePlugin and _util [hugovk] - Fix return value of FreeTypeFont.textsize() does not include font offsets From 3c39a44f6e5331432a86d486449d7019f90728a3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 19 Jul 2014 01:45:57 +0300 Subject: [PATCH 390/488] Make _make_gamma_lut() public --- PIL/ImagePalette.py | 2 +- Tests/test_imagepalette.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index ee3c22544..67bf593e6 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -134,7 +134,7 @@ def _make_linear_lut(black, white): return lut -def _make_gamma_lut(exp, mode="RGB"): +def make_gamma_lut(exp): lut = [] for i in range(256): lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5)) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index be82f4dcb..af742edd1 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -44,6 +44,25 @@ class TestImagePalette(PillowTestCase): self.assertIsInstance(p, ImagePalette) self.assertEqual(p.palette, palette.tobytes()) + def test_make_gamma_lut(self): + # Arrange + from PIL.ImagePalette import make_gamma_lut + exp = 5 + + # Act + lut = make_gamma_lut(exp) + + # Assert + self.assertIsInstance(lut, list) + self.assertEqual(len(lut), 256) + # Check a few values + self.assertEqual(lut[0], 0) + self.assertEqual(lut[63], 0) + self.assertEqual(lut[127], 8) + self.assertEqual(lut[191], 60) + self.assertEqual(lut[255], 255) + + if __name__ == '__main__': unittest.main() From 098e4c36d6da27f73ebe0fbf3770a3150f6ca5a3 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Fri, 18 Jul 2014 22:02:14 -0400 Subject: [PATCH 391/488] Further populated Exif values in TiffTags. Added lots more of the possible Exif values per the EXIF specifications. --- PIL/TiffTags.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index ccbd56507..f4420aacc 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -148,10 +148,24 @@ TAGS = { 34675: "ICCProfile", # Additional Exif Info + 33434: "ExposureTime", + 33437: "FNumber", + 34850: "ExposureProgram", + 34852: "SpectralSensitivity", + 34853: "GPSInfoIFD", + 34855: "ISOSpeedRatings", + 34856: "OECF", + 34864: "SensitivityType", + 34865: "StandardOutputSensitivity", + 34866: "RecommendedExposureIndex", + 34867: "ISOSpeed", + 34868: "ISOSpeedLatitudeyyy", + 34869: "ISOSpeedLatitudezzz", 36864: "ExifVersion", 36867: "DateTimeOriginal", 36868: "DateTImeDigitized", 37121: "ComponentsConfiguration", + 37122: "CompressedBitsPerPixel", 37377: "ShutterSpeedValue", 37378: "ApertureValue", 37379: "BrightnessValue", @@ -165,12 +179,46 @@ TAGS = { 37396: "SubjectArea", 37500: "MakerNote", 37510: "UserComment", + 37520: "SubSec", + 37521: "SubSecTimeOriginal", + 37522: "SubsecTimeDigitized", 40960: "FlashPixVersion", 40961: "ColorSpace", 40962: "PixelXDimension", 40963: "PixelYDimension", - 40965: "InteroperabilityIFDPointer", + 40964: "RelatedSoundFile", + 40965: "InteroperabilityIFD", + 41483: "FlashEnergy", + 41484: "SpatialFrequencyResponse", + 41486: "FocalPlaneXResolution", + 41487: "FocalPlaneYResolution", + 41488: "FocalPlaneResolutionUnit", + 41492: "SubjectLocation", + 41493: "ExposureIndex", + 41495: "SensingMethod", + 41728: "FileSource", + 41729: "SceneType", + 41730: "CFAPattern", + 41985: "CustomRendered", + 41986: "ExposureMode", + 41987: "WhiteBalance", + 41988: "DigitalZoomRatio", + 41989: "FocalLengthIn35mmFilm", + 41990: "SceneCaptureType", + 41991: "GainControl", + 41992: "Contrast", + 41993: "Saturation", + 41994: "Sharpness", + 41995: "DeviceSettingDescription", + 41996: "SubjectDistanceRange", 42016: "ImageUniqueID", + 42032: "CameraOwnerName", + 42033: "BodySerialNumber", + 42034: "LensSpecification", + 42035: "LensMake", + 42036: "LensModel", + 42037: "LensSerialNumber", + 42240: "Gamma", # MP Info 45056: "MPFVersion", From 65cbdae4492061f824bdbb45ed9450d91ad85162 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 19 Jul 2014 18:46:12 +0300 Subject: [PATCH 392/488] Run nose in verbose mode so we can see the tests being run/skipped. Override __str__ in PillowTestCase for nicer output, and make sure all tests are derived from PillowTestCase. --- .travis.yml | 4 ++-- Tests/helper.py | 4 ++++ Tests/test_image_tobytes.py | 4 ++-- Tests/test_pyroma.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9c1e75a1..942ee95c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,11 +33,11 @@ script: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests -v Tests/test_*.py; fi # Cover the others - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose -v Tests/test_*.py; fi after_success: - coverage report diff --git a/Tests/helper.py b/Tests/helper.py index c00e105e4..64f29bd5d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -19,6 +19,10 @@ class PillowTestCase(unittest.TestCase): # holds last result object passed to run method: self.currentResult = None + # Nicer output for --verbose + def __str__(self): + return self.__class__.__name__ + "." + self._testMethodName + def run(self, result=None): self.currentResult = result # remember result for use later unittest.TestCase.run(self, result) # call superclass run method diff --git a/Tests/test_image_tobytes.py b/Tests/test_image_tobytes.py index 3be9128c1..6dbf7d6f2 100644 --- a/Tests/test_image_tobytes.py +++ b/Tests/test_image_tobytes.py @@ -1,7 +1,7 @@ -from helper import unittest, lena +from helper import unittest, PillowTestCase, lena -class TestImageToBytes(unittest.TestCase): +class TestImageToBytes(PillowTestCase): def test_sanity(self): data = lena().tobytes() diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index c10156cc0..295ef1057 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -7,7 +7,7 @@ except ImportError: pass -class TestPyroma(unittest.TestCase): +class TestPyroma(PillowTestCase): def setUp(self): try: From 64bf7d466f20e0036cc6a45d8bbbee0f9a0f03ac Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 19 Jul 2014 18:57:21 +0300 Subject: [PATCH 393/488] Fail fast: -x to stop running tests after the first error or failure --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 942ee95c9..09e523c39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,11 +33,11 @@ script: # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests -v Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests -vx Tests/test_*.py; fi # Cover the others - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose -v Tests/test_*.py; fi + - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi after_success: - coverage report From 0c12058e37fdc9decf8aa98bcced71cbdfada55c Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 19 Jul 2014 20:21:18 +0300 Subject: [PATCH 394/488] Use unique class names to match filenames --- Tests/test_image_array.py | 2 +- Tests/test_imagefile.py | 2 +- Tests/test_imagegrab.py | 4 ++-- Tests/test_lib_image.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index a0f5f29e1..c5e49fc39 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -5,7 +5,7 @@ from PIL import Image im = lena().resize((128, 100)) -class TestImageCrop(PillowTestCase): +class TestImageArray(PillowTestCase): def test_toarray(self): def test(mode): diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index d7f7f2a56..78fb3427f 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -14,7 +14,7 @@ MAXBLOCK = ImageFile.MAXBLOCK SAFEBLOCK = ImageFile.SAFEBLOCK -class TestImagePutData(PillowTestCase): +class TestImageFile(PillowTestCase): def test_parser(self): diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 2275d34a1..13affe6b9 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase try: from PIL import ImageGrab - class TestImageCopy(PillowTestCase): + class TestImageGrab(PillowTestCase): def test_grab(self): im = ImageGrab.grab() @@ -14,7 +14,7 @@ try: self.assert_image(im, im.mode, im.size) except ImportError: - class TestImageCopy(PillowTestCase): + class TestImageGrab(PillowTestCase): def test_skip(self): self.skipTest("ImportError") diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index e0a903b00..1a16deff2 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -3,7 +3,7 @@ from helper import unittest, PillowTestCase from PIL import Image -class TestSanity(PillowTestCase): +class TestLibImage(PillowTestCase): def test_setmode(self): From 15c92359ccf750d79b3474b95aa815149564f15f Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 20 Jul 2014 00:51:26 +0300 Subject: [PATCH 395/488] Merge some similar tests to a single file --- Tests/test_image_filter.py | 4 ++++ Tests/test_image_mode.py | 21 ++++++++++++++++++++ Tests/test_image_transform.py | 16 +++++++++++++++ Tests/test_imagefilter.py | 37 ----------------------------------- Tests/test_imagemode.py | 32 ------------------------------ Tests/test_imagetransform.py | 27 ------------------------- 6 files changed, 41 insertions(+), 96 deletions(-) delete mode 100644 Tests/test_imagefilter.py delete mode 100644 Tests/test_imagemode.py delete mode 100644 Tests/test_imagetransform.py diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 4a85b0a2e..2452479cc 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -29,6 +29,10 @@ class TestImageFilter(PillowTestCase): filter(ImageFilter.MinFilter) filter(ImageFilter.ModeFilter) filter(ImageFilter.Kernel((3, 3), list(range(9)))) + filter(ImageFilter.GaussianBlur) + filter(ImageFilter.GaussianBlur(5)) + filter(ImageFilter.UnsharpMask) + filter(ImageFilter.UnsharpMask(10)) self.assertRaises(TypeError, lambda: filter("hello")) diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 25c35c607..74ed9b7aa 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -10,6 +10,27 @@ class TestImageMode(PillowTestCase): im = lena() im.mode + from PIL import ImageMode + + ImageMode.getmode("1") + ImageMode.getmode("L") + ImageMode.getmode("P") + ImageMode.getmode("RGB") + ImageMode.getmode("I") + ImageMode.getmode("F") + + m = ImageMode.getmode("1") + self.assertEqual(m.mode, "1") + self.assertEqual(m.bands, ("1",)) + self.assertEqual(m.basemode, "L") + self.assertEqual(m.basetype, "L") + + m = ImageMode.getmode("RGB") + self.assertEqual(m.mode, "RGB") + self.assertEqual(m.bands, ("R", "G", "B")) + self.assertEqual(m.basemode, "RGB") + self.assertEqual(m.basetype, "L") + def test_properties(self): def check(mode, *result): signature = ( diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 1873ee9a4..42a3d78f3 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -5,6 +5,22 @@ from PIL import Image class TestImageTransform(PillowTestCase): + def test_sanity(self): + from PIL import ImageTransform + + im = Image.new("L", (100, 100)) + + seq = tuple(range(10)) + + transform = ImageTransform.AffineTransform(seq[:6]) + im.transform((100, 100), transform) + transform = ImageTransform.ExtentTransform(seq[:4]) + im.transform((100, 100), transform) + transform = ImageTransform.QuadTransform(seq[:8]) + im.transform((100, 100), transform) + transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) + im.transform((100, 100), transform) + def test_extent(self): im = lena('RGB') (w, h) = im.size diff --git a/Tests/test_imagefilter.py b/Tests/test_imagefilter.py deleted file mode 100644 index f7edb409a..000000000 --- a/Tests/test_imagefilter.py +++ /dev/null @@ -1,37 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import ImageFilter - - -class TestImageFilter(PillowTestCase): - - def test_sanity(self): - # see test_image_filter for more tests - - # Check these run. Exceptions cause failures. - ImageFilter.MaxFilter - ImageFilter.MedianFilter - ImageFilter.MinFilter - ImageFilter.ModeFilter - ImageFilter.Kernel((3, 3), list(range(9))) - ImageFilter.GaussianBlur - ImageFilter.GaussianBlur(5) - ImageFilter.UnsharpMask - ImageFilter.UnsharpMask(10) - - ImageFilter.BLUR - ImageFilter.CONTOUR - ImageFilter.DETAIL - ImageFilter.EDGE_ENHANCE - ImageFilter.EDGE_ENHANCE_MORE - ImageFilter.EMBOSS - ImageFilter.FIND_EDGES - ImageFilter.SMOOTH - ImageFilter.SMOOTH_MORE - ImageFilter.SHARPEN - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/Tests/test_imagemode.py b/Tests/test_imagemode.py deleted file mode 100644 index 2c5730d74..000000000 --- a/Tests/test_imagemode.py +++ /dev/null @@ -1,32 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import ImageMode - - -class TestImageMode(PillowTestCase): - - def test_sanity(self): - ImageMode.getmode("1") - ImageMode.getmode("L") - ImageMode.getmode("P") - ImageMode.getmode("RGB") - ImageMode.getmode("I") - ImageMode.getmode("F") - - m = ImageMode.getmode("1") - self.assertEqual(m.mode, "1") - self.assertEqual(m.bands, ("1",)) - self.assertEqual(m.basemode, "L") - self.assertEqual(m.basetype, "L") - - m = ImageMode.getmode("RGB") - self.assertEqual(m.mode, "RGB") - self.assertEqual(m.bands, ("R", "G", "B")) - self.assertEqual(m.basemode, "RGB") - self.assertEqual(m.basetype, "L") - - -if __name__ == '__main__': - unittest.main() - -# End of file diff --git a/Tests/test_imagetransform.py b/Tests/test_imagetransform.py deleted file mode 100644 index f5741df32..000000000 --- a/Tests/test_imagetransform.py +++ /dev/null @@ -1,27 +0,0 @@ -from helper import unittest, PillowTestCase - -from PIL import Image -from PIL import ImageTransform - - -class TestImageTransform(PillowTestCase): - - def test_sanity(self): - im = Image.new("L", (100, 100)) - - seq = tuple(range(10)) - - transform = ImageTransform.AffineTransform(seq[:6]) - im.transform((100, 100), transform) - transform = ImageTransform.ExtentTransform(seq[:4]) - im.transform((100, 100), transform) - transform = ImageTransform.QuadTransform(seq[:8]) - im.transform((100, 100), transform) - transform = ImageTransform.MeshTransform([(seq[:4], seq[:8])]) - im.transform((100, 100), transform) - - -if __name__ == '__main__': - unittest.main() - -# End of file From fef3ceb2c02ef241a508ee020fe2617e10e33e42 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 20 Jul 2014 01:50:05 +0300 Subject: [PATCH 396/488] If we can't read a file due to unsupported compression, raise an error --- PIL/SgiImagePlugin.py | 10 +++++----- Tests/test_file_sgi.py | 18 +++--------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/PIL/SgiImagePlugin.py b/PIL/SgiImagePlugin.py index bf3c18a43..2b8fcd8e4 100644 --- a/PIL/SgiImagePlugin.py +++ b/PIL/SgiImagePlugin.py @@ -45,7 +45,7 @@ class SgiImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(512) if i16(s) != 474: - raise SyntaxError("not an SGI image file") + raise ValueError("Not an SGI image file") # relevant header entries compression = i8(s[2]) @@ -61,7 +61,7 @@ class SgiImageFile(ImageFile.ImageFile): elif layout == (1, 3, 4): self.mode = "RGBA" else: - raise SyntaxError("unsupported SGI image mode") + raise ValueError("Unsupported SGI image mode") # size self.size = i16(s[6:]), i16(s[8:]) @@ -76,8 +76,7 @@ class SgiImageFile(ImageFile.ImageFile): ("raw", (0, 0)+self.size, offset, (layer, 0, -1))) offset = offset + pagesize elif compression == 1: - self.tile = [ - ("sgi_rle", (0, 0)+self.size, 512, (self.mode, 0, -1))] + raise ValueError("SGI RLE encoding not supported") # # registry @@ -87,5 +86,6 @@ Image.register_open("SGI", SgiImageFile, _accept) Image.register_extension("SGI", ".bw") Image.register_extension("SGI", ".rgb") Image.register_extension("SGI", ".rgba") - Image.register_extension("SGI", ".sgi") + +# End of file diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index e94f0d989..84b184b63 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -5,44 +5,32 @@ from PIL import Image class TestFileSgi(PillowTestCase): - def sanity(self, filename, expected_mode, expected_size=(128, 128)): - # Act - im = Image.open(filename) - - # Assert - self.assertEqual(im.mode, expected_mode) - self.assertEqual(im.size, expected_size) - def test_rgb(self): # Arrange # Created with ImageMagick then renamed: # convert lena.ppm lena.sgi test_file = "Tests/images/lena.rgb" - expected_mode = "RGB" # Act / Assert - self.sanity(test_file, expected_mode) + self.assertRaises(ValueError, lambda: Image.open(test_file)) def test_l(self): # Arrange # Created with ImageMagick then renamed: # convert lena.ppm -monochrome lena.sgi test_file = "Tests/images/lena.bw" - expected_mode = "L" # Act / Assert - self.sanity(test_file, expected_mode) + self.assertRaises(ValueError, lambda: Image.open(test_file)) def test_rgba(self): # Arrange # Created with ImageMagick: # convert transparent.png transparent.sgi test_file = "Tests/images/transparent.sgi" - expected_mode = "RGBA" - expected_size = (200, 150) # Act / Assert - self.sanity(test_file, expected_mode, expected_size) + self.assertRaises(ValueError, lambda: Image.open(test_file)) if __name__ == '__main__': From 6381d6250375f290b02a249d8b01e7836abab46a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 19 Jul 2014 21:25:25 -0700 Subject: [PATCH 397/488] Update CHANGES.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index da7ffcb84..5652479a1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -16,7 +16,7 @@ Changelog (Pillow) - Added docs for ExifTags [Wintermute3] -- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, SpiderImagePlugin, XpmImagePlugin and _util +- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util [hugovk] - Fix return value of FreeTypeFont.textsize() does not include font offsets From 37691bc1e4d3f2bbf444e53510db4fecf81fe7b1 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 20 Jul 2014 10:13:26 +0300 Subject: [PATCH 398/488] Make _make_linear_lut public and issue deprecation warnings from old private methods --- PIL/ImagePalette.py | 21 ++++++++++++++++ Tests/test_imagepalette.py | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 67bf593e6..3b9e96172 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -17,6 +17,7 @@ # import array +import warnings from PIL import Image, ImageColor @@ -125,6 +126,26 @@ def raw(rawmode, data): # Factories def _make_linear_lut(black, white): + warnings.warn( + '_make_linear_lut() is deprecated. ' + 'Please call make_linear_lut() instead.', + DeprecationWarning, + stacklevel=2 + ) + return make_linear_lut(black, white) + + +def _make_gamma_lut(exp): + warnings.warn( + '_make_gamma_lut() is deprecated. ' + 'Please call make_gamma_lut() instead.', + DeprecationWarning, + stacklevel=2 + ) + return make_gamma_lut(exp) + + +def make_linear_lut(black, white): lut = [] if black == 0: for i in range(256): diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index af742edd1..8356b5d60 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -44,6 +44,34 @@ class TestImagePalette(PillowTestCase): self.assertIsInstance(p, ImagePalette) self.assertEqual(p.palette, palette.tobytes()) + def test_make_linear_lut(self): + # Arrange + from PIL.ImagePalette import make_linear_lut + black = 0 + white = 255 + + # Act + lut = make_linear_lut(black, white) + + # Assert + self.assertIsInstance(lut, list) + self.assertEqual(len(lut), 256) + # Check values + for i in range(0, len(lut)): + self.assertEqual(lut[i], i) + + def test_make_linear_lut_not_yet_implemented(self): + # Update after FIXME + # Arrange + from PIL.ImagePalette import make_linear_lut + black = 1 + white = 255 + + # Act + self.assertRaises( + NotImplementedError, + lambda: make_linear_lut(black, white)) + def test_make_gamma_lut(self): # Arrange from PIL.ImagePalette import make_gamma_lut @@ -62,6 +90,27 @@ class TestImagePalette(PillowTestCase): self.assertEqual(lut[191], 60) self.assertEqual(lut[255], 255) + def test_private_make_linear_lut_warning(self): + # Arrange + from PIL.ImagePalette import _make_linear_lut + black = 0 + white = 255 + + # Act / Assert + self.assert_warning( + DeprecationWarning, + lambda: _make_linear_lut(black, white)) + + def test_private_make_gamma_lut_warning(self): + # Arrange + from PIL.ImagePalette import _make_gamma_lut + exp = 5 + + # Act / Assert + self.assert_warning( + DeprecationWarning, + lambda: _make_gamma_lut(exp)) + if __name__ == '__main__': unittest.main() From 751f67205838f37e55fb3865f9b6410d7f5e89b1 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 20 Jul 2014 21:08:14 +0300 Subject: [PATCH 399/488] Call public, non-warning versions internally --- PIL/ImagePalette.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 3b9e96172..e59e7a88a 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -182,9 +182,9 @@ def random(mode="RGB"): def sepia(white="#fff0c0"): r, g, b = ImageColor.getrgb(white) - r = _make_linear_lut(0, r) - g = _make_linear_lut(0, g) - b = _make_linear_lut(0, b) + r = make_linear_lut(0, r) + g = make_linear_lut(0, g) + b = make_linear_lut(0, b) return ImagePalette("RGB", r + g + b) From 8c60de375d8b76d487f99964edbf26e7596ea2aa Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 21 Jul 2014 12:15:23 +0300 Subject: [PATCH 400/488] Workaround to test PyQt: https://github.com/travis-ci/travis-ci/issues/2219#issuecomment-41804942 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 09e523c39..b37588843 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "pypy" - 2.6 - 2.7 + - "2.7_with_system_site_packages" # For PyQt4 - 3.2 - 3.3 - 3.4 From d06735b49eba1e10e8e498755414ceb0ce778168 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 21 Jul 2014 23:18:46 +0300 Subject: [PATCH 401/488] More ImagePalette.py tests and remove unused and uncallable new() --- PIL/ImagePalette.py | 6 +----- Tests/test_imagepalette.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index e59e7a88a..62f8814f1 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -18,7 +18,7 @@ import array import warnings -from PIL import Image, ImageColor +from PIL import ImageColor class ImagePalette: @@ -162,10 +162,6 @@ def make_gamma_lut(exp): return lut -def new(mode, data): - return Image.core.new_palette(mode, data) - - def negative(mode="RGB"): palette = list(range(256)) palette.reverse() diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 8356b5d60..e56c61390 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -111,6 +111,41 @@ class TestImagePalette(PillowTestCase): DeprecationWarning, lambda: _make_gamma_lut(exp)) + def test_rawmode_valueerrors(self): + # Arrange + from PIL.ImagePalette import raw + palette = raw("RGB", list(range(256))*3) + + # Act / Assert + self.assertRaises(ValueError, lambda: palette.tobytes()) + self.assertRaises(ValueError, lambda: palette.getcolor((1, 2, 3))) + f = self.tempfile("temp.lut") + self.assertRaises(ValueError, lambda: palette.save(f)) + + def test_getdata(self): + # Arrange + data_in = list(range(256))*3 + palette = ImagePalette("RGB", data_in) + + # Act + mode, data_out = palette.getdata() + + # Assert + self.assertEqual(mode, "RGB;L") + + def test_rawmode_getdata(self): + # Arrange + from PIL.ImagePalette import raw + data_in = list(range(256))*3 + palette = raw("RGB", data_in) + + # Act + rawmode, data_out = palette.getdata() + + # Assert + self.assertEqual(rawmode, "RGB") + self.assertEqual(data_in, data_out) + if __name__ == '__main__': unittest.main() From d5909e49467e471b3c746cfe6a93872d88bd72cf Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Tue, 22 Jul 2014 13:31:51 -0400 Subject: [PATCH 402/488] Basic MPO reading works, seek is partially there. --- PIL/Image.py | 1 - PIL/JpegImagePlugin.py | 20 ++++++++++++++++++++ PIL/MpoImagePlugin.py | 31 +++++++++++++++++++------------ Tests/test_file_mpo.py | 20 ++++++++++++++++++++ 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index b4aa11e20..eb08fab9e 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2231,7 +2231,6 @@ def open(fp, mode="r"): for i in ID: try: - print(ID) factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 7dfdfa308..ce225ddc1 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -37,6 +37,7 @@ __version__ = "0.6" import array import struct import io +from struct import unpack from PIL import Image, ImageFile, TiffImagePlugin, _binary from PIL.JpegPresets import presets from PIL._util import isStringType @@ -114,6 +115,7 @@ def APP(self, marker): elif marker == 0xFFE2 and s[:4] == b"MPF\0": # extract MPO information self.info["mp"] = s[4:] + self.info["mpoffset"] = self.fp.tell() def COM(self, marker): @@ -451,12 +453,30 @@ def _getmp(self): return None file = io.BytesIO(data) head = file.read(8) + endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<' mp = {} # process dictionary info = TiffImagePlugin.ImageFileDirectory(head) info.load(file) for key, value in info.items(): mp[key] = _fixup(value) + # it's an error not to have a number of images + try: + quant = mp[0xB001] + except KeyError: + raise SyntaxError("malformed MP Index (no number of images)") + # get MP entries + try: + mpentries = [] + for entrynum in range(0, quant): + rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16] + unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry) + labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2') + mpentry = dict(zip(labels, unpackedentry)) + mpentries.append(mpentry) + mp[0xB002] = mpentries + except KeyError: + raise SyntaxError("malformed MP Index (bad MP Entry)") return mp diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 18f32bd48..6accf5110 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -38,21 +38,28 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): def _open(self): JpegImagePlugin.JpegImageFile._open(self) + self.mpinfo = self._getmp() + self.__framecount = self.mpinfo[0xB001] + self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \ + for mpent in self.mpinfo[0xB002]] + assert self.__framecount == len(self.__mpoffsets) + del self.info['mpoffset'] # no longer needed self.__fp = self.fp # FIXME: hack - self.__rewind = self.fp.tell() - self.seek(0) # get ready to read first frame + self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame + self.__frame = 0 + self.offset = 0 def seek(self, frame): - - if frame == 0: - # rewind - self.__offset = 0 - self.dispose = None - self.__frame = -1 - self.__fp.seek(self.__rewind) - - if frame != self.__frame + 1: - raise ValueError("cannot seek to frame %d" % frame) + if frame < 0 or frame >= self.__framecount: + raise EOFError("no more images in MPO file") + else: + self.fp = self.__fp + self.fp.seek(self.__mpoffsets[frame]) + self.offset = self.__mpoffsets[frame] + rawmode = self.mode + if self.mode == "CMYK": + rawmode = "CMYK;I" # assume adobe conventions + self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] self.__frame = frame def tell(self): diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 17f9a66fb..a0bcf6f40 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -54,6 +54,26 @@ class TestFileMpo(PillowTestCase): info = im._getmp() self.assertEqual(info[45056], '0100') self.assertEqual(info[45057], 2) + + def test_seek(self): + for test_file in test_files: + im = Image.open(test_file) + self.assertEqual(im.tell(), 0) + # prior to first image raises an error, both blatant and borderline + self.assertRaises(EOFError, im.seek, -1) + self.assertRaises(EOFError, im.seek, -523) + # after the final image raises an error, both blatant and borderline + self.assertRaises(EOFError, im.seek, 2) + self.assertRaises(EOFError, im.seek, 523) + # bad calls shouldn't change the frame + self.assertEqual(im.tell(), 0) + # this one will work + im.seek(1) + self.assertEqual(im.tell(), 1) + # and this one, too + im.seek(0) + self.assertEqual(im.tell(), 0) + if __name__ == '__main__': unittest.main() From 1d3fe7ff4544b0670d4fdeddba117cac56c77d80 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Tue, 22 Jul 2014 18:23:45 -0400 Subject: [PATCH 403/488] Corrected offset for MPO frames. --- PIL/JpegImagePlugin.py | 3 ++- PIL/MpoImagePlugin.py | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index ce225ddc1..bedc42373 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -115,7 +115,8 @@ def APP(self, marker): elif marker == 0xFFE2 and s[:4] == b"MPF\0": # extract MPO information self.info["mp"] = s[4:] - self.info["mpoffset"] = self.fp.tell() + # offset is current location minus buffer size plus constant header size + self.info["mpoffset"] = self.fp.tell() - n + 4 def COM(self, marker): diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 6accf5110..6594b74b0 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -42,6 +42,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): self.__framecount = self.mpinfo[0xB001] self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \ for mpent in self.mpinfo[0xB002]] + self.__mpoffsets[0] = 0 assert self.__framecount == len(self.__mpoffsets) del self.info['mpoffset'] # no longer needed self.__fp = self.fp # FIXME: hack @@ -56,10 +57,6 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): self.fp = self.__fp self.fp.seek(self.__mpoffsets[frame]) self.offset = self.__mpoffsets[frame] - rawmode = self.mode - if self.mode == "CMYK": - rawmode = "CMYK;I" # assume adobe conventions - self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))] self.__frame = frame def tell(self): From d34fd8868b8fe86d197a88c717b86c8ba37c1d80 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 21 Jul 2014 12:15:23 +0300 Subject: [PATCH 404/488] Workaround to test PyQt: https://github.com/travis-ci/travis-ci/issues/2219#issuecomment-41804942 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 09e523c39..b37588843 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "pypy" - 2.6 - 2.7 + - "2.7_with_system_site_packages" # For PyQt4 - 3.2 - 3.3 - 3.4 From ee4793a806f34c428f059f0e4256c61bad351f60 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2014 22:29:38 -0700 Subject: [PATCH 405/488] More detail when assert_image_similar fails --- Tests/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/helper.py b/Tests/helper.py index 64f29bd5d..082fb93f9 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -100,7 +100,7 @@ class PillowTestCase(unittest.TestCase): ave_diff = float(diff)/(a.size[0]*a.size[1]) self.assertGreaterEqual( epsilon, ave_diff, - msg or "average pixel value difference %.4f > epsilon %.4f" % ( + (msg or '') + " average pixel value difference %.4f > epsilon %.4f" % ( ave_diff, epsilon)) def assert_warning(self, warn_class, func): From 625ff24358b3705791450fd68b0f9ad8e8afca98 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2014 22:31:32 -0700 Subject: [PATCH 406/488] Storage, packing and access for HSV format images --- PIL/Image.py | 1 + PIL/PyAccess.py | 1 + Tests/make_hash.py | 1 + libImaging/Access.c | 5 +++-- libImaging/Pack.c | 6 ++++++ libImaging/Storage.c | 7 +++++++ libImaging/Unpack.c | 6 ++++++ 7 files changed, 25 insertions(+), 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index ea8cc6155..a61aaa62b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -220,6 +220,7 @@ _MODEINFO = { "CMYK": ("RGB", "L", ("C", "M", "Y", "K")), "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), "LAB": ("RGB", "L", ("L", "A", "B")), + "HSV": ("RGB", "L", ("H", "S", "V")), # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and # BGR;24. Use these modes only if you know exactly what you're diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index f76beb820..7ccc313eb 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -261,6 +261,7 @@ mode_map = {'1': _PyAccess8, 'PA': _PyAccess32_2, 'RGB': _PyAccess32_3, 'LAB': _PyAccess32_3, + 'HSV': _PyAccess32_3, 'YCbCr': _PyAccess32_3, 'RGBA': _PyAccess32_4, 'RGBa': _PyAccess32_4, diff --git a/Tests/make_hash.py b/Tests/make_hash.py index 32196e9f8..88bb2994b 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -9,6 +9,7 @@ modes = [ "RGB", "RGBA", "RGBa", "RGBX", "CMYK", "YCbCr", + "LAB", "HSV", ] diff --git a/libImaging/Access.c b/libImaging/Access.c index 62c97f3a3..97474a0b8 100644 --- a/libImaging/Access.c +++ b/libImaging/Access.c @@ -13,8 +13,8 @@ #include "Imaging.h" /* use Tests/make_hash.py to calculate these values */ -#define ACCESS_TABLE_SIZE 21 -#define ACCESS_TABLE_HASH 30197 +#define ACCESS_TABLE_SIZE 27 +#define ACCESS_TABLE_HASH 3078 static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE]; @@ -238,6 +238,7 @@ ImagingAccessInit() ADD("CMYK", line_32, get_pixel_32, put_pixel_32); ADD("YCbCr", line_32, get_pixel_32, put_pixel_32); ADD("LAB", line_32, get_pixel_32, put_pixel_32); + ADD("HSV", line_32, get_pixel_32, put_pixel_32); } ImagingAccess diff --git a/libImaging/Pack.c b/libImaging/Pack.c index 1cc1f3a94..fecafbde4 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -566,6 +566,12 @@ static struct { {"LAB", "A", 8, band1}, {"LAB", "B", 8, band2}, + /* HSV */ + {"HSV", "HSV", 24, ImagingPackRGB}, + {"HSV", "H", 8, band0}, + {"HSV", "S", 8, band1}, + {"HSV", "V", 8, band2}, + /* integer */ {"I", "I", 32, copy4}, {"I", "I;16B", 16, packI16B}, diff --git a/libImaging/Storage.c b/libImaging/Storage.c index c6d2e5c5e..d65de1c0a 100644 --- a/libImaging/Storage.c +++ b/libImaging/Storage.c @@ -186,6 +186,13 @@ ImagingNewPrologueSubtype(const char *mode, unsigned xsize, unsigned ysize, im->pixelsize = 4; im->linesize = xsize * 4; + } else if (strcmp(mode, "HSV") == 0) { + /* 24-bit color, luminance, + 2 color channels */ + /* L is uint8, a,b are int8 */ + im->bands = 3; + im->pixelsize = 4; + im->linesize = xsize * 4; + } else { free(im); return (Imaging) ImagingError_ValueError("unrecognized mode"); diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index 552c759b9..7c453dbfd 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -1159,6 +1159,12 @@ static struct { {"LAB", "A", 8, band1}, {"LAB", "B", 8, band2}, + /* HSV Color */ + {"HSV", "HSV", 24, ImagingUnpackRGB}, + {"HSV", "H", 8, band0}, + {"HSV", "S", 8, band1}, + {"HSV", "V", 8, band2}, + /* integer variations */ {"I", "I", 32, copy4}, {"I", "I;8", 8, unpackI8}, From 0bb1cd398fc5ba14e69795eb26f709a7b0bf2436 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 21 Jul 2014 22:36:40 -0700 Subject: [PATCH 407/488] Conversion between RGB and HSV images --- Tests/test_format_hsv.py | 177 +++++++++++++++++++++++++++++++++++++++ libImaging/Convert.c | 126 ++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 Tests/test_format_hsv.py diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py new file mode 100644 index 000000000..1a9c46bd7 --- /dev/null +++ b/Tests/test_format_hsv.py @@ -0,0 +1,177 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image + +import colorsys, itertools + +class TestFormatHSV(PillowTestCase): + + def int_to_float(self, i): + return float(i)/255.0 + def str_to_float(self, i): + return float(ord(i))/255.0 + def to_int(self, f): + return int(f*255.0) + + def test_sanity(self): + im = Image.new('HSV', (100,100)) + + def wedge(self): + w =Image._wedge() + w90 = w.rotate(90) + + (px, h) = w.size + + r = Image.new('L', (px*3,h)) + g = r.copy() + b = r.copy() + + r.paste(w, (0,0)) + r.paste(w90, (px,0)) + + g.paste(w90, (0,0)) + g.paste(w, (2*px,0)) + + b.paste(w, (px,0)) + b.paste(w90, (2*px,0)) + + img = Image.merge('RGB',(r,g,b)) + + #print (("%d, %d -> "% (int(1.75*px),int(.25*px))) + \ + # "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px))) + #print (("%d, %d -> "% (int(.75*px),int(.25*px))) + \ + # "(%s, %s, %s)"%img.getpixel((.75*px, .25*px))) + return img + + def to_xxx_colorsys(self, im, func, mode): + # convert the hard way using the library colorsys routines. + + (r,g,b) = im.split() + + if bytes is str: + f_r = map(self.str_to_float,r.tobytes()) + f_g = map(self.str_to_float,g.tobytes()) + f_b = map(self.str_to_float,b.tobytes()) + else: + f_r = map(self.int_to_float,r.tobytes()) + f_g = map(self.int_to_float,g.tobytes()) + f_b = map(self.int_to_float,b.tobytes()) + + f_h = []; + f_s = []; + f_v = []; + + if hasattr(itertools, 'izip'): + iter_helper = itertools.izip + else: + iter_helper = itertools.zip_longest + + for (_r, _g, _b) in iter_helper(f_r, f_g, f_b): + _h, _s, _v = func(_r, _g, _b) + f_h.append(_h) + f_s.append(_s) + f_v.append(_v) + + h = Image.new('L', r.size) + h.putdata(list(map(self.to_int, f_h))) + s = Image.new('L', r.size) + s.putdata(list(map(self.to_int, f_s))) + v = Image.new('L', r.size) + v.putdata(list(map(self.to_int, f_v))) + + hsv = Image.merge(mode, (h, s, v)) + + return hsv + + def to_hsv_colorsys(self, im): + return self.to_xxx_colorsys(im, colorsys.rgb_to_hsv, 'HSV') + + def to_rgb_colorsys(self, im): + return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, 'RGB') + + def test_wedge(self): + im = self.wedge().convert('HSV') + comparable = self.to_hsv_colorsys(self.wedge()) + + #print (im.getpixel((448, 64))) + #print (comparable.getpixel((448, 64))) + + #print(im.split()[0].histogram()) + #print(comparable.split()[0].histogram()) + + #im.split()[0].show() + #comparable.split()[0].show() + + self.assert_image_similar(im.split()[0], comparable.split()[0], + 1, "Hue conversion is wrong") + self.assert_image_similar(im.split()[1], comparable.split()[1], + 1, "Saturation conversion is wrong") + self.assert_image_similar(im.split()[2], comparable.split()[2], + 1, "Value conversion is wrong") + + #print (im.getpixel((192, 64))) + + comparable = self.wedge() + im = im.convert('RGB') + + #im.split()[0].show() + #comparable.split()[0].show() + #print (im.getpixel((192, 64))) + #print (comparable.getpixel((192, 64))) + + self.assert_image_similar(im.split()[0], comparable.split()[0], + 3, "R conversion is wrong") + self.assert_image_similar(im.split()[1], comparable.split()[1], + 3, "G conversion is wrong") + self.assert_image_similar(im.split()[2], comparable.split()[2], + 3, "B conversion is wrong") + + + def test_convert(self): + im = lena('RGB').convert('HSV') + comparable = self.to_hsv_colorsys(lena('RGB')) + +# print ([ord(x) for x in im.split()[0].tobytes()[:80]]) +# print ([ord(x) for x in comparable.split()[0].tobytes()[:80]]) + +# print(im.split()[0].histogram()) +# print(comparable.split()[0].histogram()) + + self.assert_image_similar(im.split()[0], comparable.split()[0], + 1, "Hue conversion is wrong") + self.assert_image_similar(im.split()[1], comparable.split()[1], + 1, "Saturation conversion is wrong") + self.assert_image_similar(im.split()[2], comparable.split()[2], + 1, "Value conversion is wrong") + + + def test_hsv_to_rgb(self): + comparable = self.to_hsv_colorsys(lena('RGB')) + converted = comparable.convert('RGB') + comparable = self.to_rgb_colorsys(comparable) + + # print(converted.split()[1].histogram()) + # print(target.split()[1].histogram()) + + # print ([ord(x) for x in target.split()[1].tobytes()[:80]]) + # print ([ord(x) for x in converted.split()[1].tobytes()[:80]]) + + + self.assert_image_similar(converted.split()[0], comparable.split()[0], + 3, "R conversion is wrong") + self.assert_image_similar(converted.split()[1], comparable.split()[1], + 3, "G conversion is wrong") + self.assert_image_similar(converted.split()[2], comparable.split()[2], + 3, "B conversion is wrong") + + + + + + + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/libImaging/Convert.c b/libImaging/Convert.c index 631263b31..4eb106c27 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -35,6 +35,9 @@ #include "Imaging.h" +#define MAX(a, b) (a)>(b) ? (a) : (b) +#define MIN(a, b) (a)<(b) ? (a) : (b) + #define CLIP(v) ((v) <= 0 ? 0 : (v) >= 255 ? 255 : (v)) #define CLIP16(v) ((v) <= -32768 ? -32768 : (v) >= 32767 ? 32767 : (v)) @@ -236,6 +239,126 @@ rgb2bgr24(UINT8* out, const UINT8* in, int xsize) } } +static void +rgb2hsv(UINT8* out, const UINT8* in, int xsize) +{ // following colorsys.py + float h,s,rc,gc,bc,cr; + UINT8 maxc,minc; + UINT8 r, g, b; + UINT8 uh,us,uv; + int x; + + for (x = 0; x < xsize; x++, in += 4) { + r = in[0]; + g = in[1]; + b = in[2]; + + maxc = MAX(r,MAX(g,b)); + minc = MIN(r,MIN(g,b)); + uv = maxc; + if (minc == maxc){ + *out++ = 0; + *out++ = 0; + *out++ = uv; + } else { + cr = (float)(maxc-minc); + s = cr/(float)maxc; + rc = ((float)(maxc-r))/cr; + gc = ((float)(maxc-g))/cr; + bc = ((float)(maxc-b))/cr; + if (r == maxc) { + h = bc-gc; + } else if (g == maxc) { + h = 2.0 + rc-bc; + } else { + h = 4.0 + gc-rc; + } + // incorrect hue happens if h/6 is negative. + h = fmod((h/6.0 + 1.0), 1.0); + + uh = (UINT8)CLIP((int)(h*255.0)); + us = (UINT8)CLIP((int)(s*255.0)); + + *out++ = uh; + *out++ = us; + *out++ = uv; + + } + *out++ = in[3]; + } +} + +static void +hsv2rgb(UINT8* out, const UINT8* in, int xsize) +{ // following colorsys.py + + int p,q,t; + uint up,uq,ut; + int i, x; + float f, fs; + uint h,s,v; + + for (x = 0; x < xsize; x++, in += 4) { + h = in[0]; + s = in[1]; + v = in[2]; + + if (s==0){ + *out++ = v; + *out++ = v; + *out++ = v; + } else { + i = floor((float)h * 6.0 / 255.0); // 0 - 6 + f = (float)h * 6.0 / 255.0 - (float)i; // 0-1 : remainder. + fs = ((float)s)/255.0; + + p = round((float)v * (1.0-fs)); + q = round((float)v * (1.0-fs*f)); + t = round((float)v * (1.0-fs*(1.0-f))); + up = (UINT8)CLIP(p); + uq = (UINT8)CLIP(q); + ut = (UINT8)CLIP(t); + + switch (i%6) { + case 0: + *out++ = v; + *out++ = ut; + *out++ = up; + break; + case 1: + *out++ = uq; + *out++ = v; + *out++ = up; + break; + case 2: + *out++ = up; + *out++ = v; + *out++ = ut; + break; + case 3: + *out++ = up; + *out++ = uq; + *out++ = v; + break; + case 4: + *out++ = ut; + *out++ = up; + *out++ = v; + break; + case 5: + *out++ = v; + *out++ = up; + *out++ = uq; + break; + + } + } + *out++ = in[3]; + } +} + + + /* ---------------- */ /* RGBA conversions */ /* ---------------- */ @@ -658,6 +781,7 @@ static struct { { "RGB", "RGBX", rgb2rgba }, { "RGB", "CMYK", rgb2cmyk }, { "RGB", "YCbCr", ImagingConvertRGB2YCbCr }, + { "RGB", "HSV", rgb2hsv }, { "RGBA", "1", rgb2bit }, { "RGBA", "L", rgb2l }, @@ -687,6 +811,8 @@ static struct { { "YCbCr", "L", ycbcr2l }, { "YCbCr", "RGB", ImagingConvertYCbCr2RGB }, + { "HSV", "RGB", hsv2rgb }, + { "I", "I;16", I_I16L }, { "I;16", "I", I16L_I }, { "L", "I;16", L_I16L }, From 55792f8927263e9935469e375c3fbc0ed1fe15b0 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Jul 2014 16:22:28 -0700 Subject: [PATCH 408/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5652479a1..c93572cbd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Removed unusable ImagePalette.new() + [hugovk] + - Fix Scrambled XPM #808 [wiredfool] @@ -16,7 +19,7 @@ Changelog (Pillow) - Added docs for ExifTags [Wintermute3] -- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util +- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util [hugovk] - Fix return value of FreeTypeFont.textsize() does not include font offsets From 1b170dad680ae0769dfb94bb720fe3807d899041 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 22 Jul 2014 16:43:08 -0700 Subject: [PATCH 409/488] Partial opacity text example, parameters on ImageDraw.Draw [ci skip] --- docs/reference/ImageDraw.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 30aa15a9b..c24a9dd99 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -74,6 +74,34 @@ To load a OpenType/TrueType font, use the truetype function in the :py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party libraries, and may not available in all PIL builds. +Example: Draw Partial Opacity Text +---------------------------------- + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + # get an image + base = Image.open('Pillow/Tests/images/lena.png').convert('RGBA') + + # make a blank image for the text, initialized to transparent text color + txt = Image.new('RGBA', base.size, (255,255,255,0)) + + # get a font + fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40) + # get a drawing context + d = ImageDraw.Draw(txt) + + # draw text, half opacity + d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) + # draw text, full opacity + d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) + + out = Image.alpha_composite(base, txt) + + out.show() + + + Functions --------- @@ -83,6 +111,13 @@ Functions Note that the image will be modified in place. + :param im: The image to draw in. + :param mode: Optional mode to use for color values. For RGB + images, this argument can be RGB or RGBA (to blend the + drawing into the image). For all other modes, this argument + must be the same as the image mode. If omitted, the mode + defaults to the mode of the image. + Methods ------- From c469dd9ae56961c3f7ee7289a380c43d90ad57d5 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 23 Jul 2014 00:24:18 -0700 Subject: [PATCH 410/488] Added support for encoding and decoding iTXt chunks. --- PIL/PngImagePlugin.py | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index e794ef702..34fff1a31 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -147,6 +147,17 @@ class ChunkStream: return cids +# -------------------------------------------------------------------- +# Subclass of string to allow iTXt chunks to look like strings while +# keeping their extra information + +class iTXt(str): + @staticmethod + def __new__(cls, text, lang, tkey): + self = str.__new__(cls, text) + self.lang = lang + self.tkey = tkey + return self # -------------------------------------------------------------------- # PNG chunk container (for use with save(pnginfo=)) @@ -159,7 +170,26 @@ class PngInfo: def add(self, cid, data): self.chunks.append((cid, data)) + def add_itxt(self, key, value, lang="", tkey="", zip=False): + if not isinstance(key, bytes): + key = key.encode("latin-1", "strict") + if not isinstance(value, bytes): + value = value.encode("utf-8", "strict") + if not isinstance(lang, bytes): + lang = lang.encode("utf-8", "strict") + if not isinstance(tkey, bytes): + tkey = tkey.encode("utf-8", "strict") + + if zip: + import zlib + self.add(b"iTXt", key + b"\0\x01\0" + lang + b"\0" + tkey + b"\0" + zlib.compress(value)) + else: + self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) + def add_text(self, key, value, zip=0): + if isinstance(value, iTXt): + return self.add_itxt(key, value, value.lang, value.tkey, bool(zip)) + # The tEXt chunk stores latin-1 text if not isinstance(key, bytes): key = key.encode('latin-1', 'strict') @@ -329,6 +359,43 @@ class PngStream(ChunkStream): self.im_info[k] = self.im_text[k] = v return s + def chunk_iTXt(self, pos, length): + + # international text + r = s = ImageFile._safe_read(self.fp, length) + try: + k, r = r.split(b"\0", 1) + except ValueError: + return s + if len(r) < 2: + return s + cf, cm, r = i8(r[0]), i8(r[1]), r[2:] + try: + lang, tk, v = r.split(b"\0", 2) + except ValueError: + return s + if cf != 0: + if cm == 0: + import zlib + try: + v = zlib.decompress(v) + except zlib.error: + return s + else: + return s + if bytes is not str: + try: + k = k.decode("latin-1", "strict") + lang = lang.decode("utf-8", "strict") + tk = tk.decode("utf-8", "strict") + v = v.decode("utf-8", "strict") + except UnicodeError: + return s + + self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) + + return s + # -------------------------------------------------------------------- # PNG reader From 2b4d91ed531f7b4a4cd9b266d98ad9d32796c333 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 23 Jul 2014 01:09:06 -0700 Subject: [PATCH 411/488] Added iTXt tests. --- Tests/test_file_png.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index de96fdf3e..9d20ba2ab 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -129,6 +129,39 @@ class TestFilePng(PillowTestCase): HEAD + chunk(b'zTXt', b'spam\0\0' + zlib.compress(b'egg')) + TAIL) self.assertEqual(im.info, {'spam': 'egg'}) + def test_bad_itxt(self): + + im = load(HEAD + chunk(b'iTXt') + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam') + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam\0') + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam\0\x02') + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam\0\0\0foo\0') + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam\0\0\0en\0Spam\0egg') + TAIL) + self.assertEqual(im.info, {"spam": "egg"}) + self.assertEqual(im.info["spam"].lang, "en") + self.assertEqual(im.info["spam"].tkey, "Spam") + + im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' + zlib.compress(b"egg")[:1]) + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam\0\1\1en\0Spam\0' + zlib.compress(b"egg")) + TAIL) + self.assertEqual(im.info, {}) + + im = load(HEAD + chunk(b'iTXt', b'spam\0\1\0en\0Spam\0' + zlib.compress(b"egg")) + TAIL) + self.assertEqual(im.info, {"spam": "egg"}) + self.assertEqual(im.info["spam"].lang, "en") + self.assertEqual(im.info["spam"].tkey, "Spam") + def test_interlace(self): file = "Tests/images/pil123p.png" From 823d377e4751335be16fcf03dc85236360725c7f Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 23 Jul 2014 07:27:51 -0700 Subject: [PATCH 412/488] Added tests for iTXt saving. --- Tests/test_file_png.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 9d20ba2ab..fbccfb9f6 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -265,6 +265,22 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.info, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) self.assertEqual(im.text, {'TXT': 'VALUE', 'ZIP': 'VALUE'}) + def test_roundtrip_itxt(self): + # Check iTXt roundtripping + + im = Image.new("RGB", (32, 32)) + info = PngImagePlugin.PngInfo() + info.add_itxt("spam", "Eggs", "en", "Spam") + info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"), zip=True) + + im = roundtrip(im, pnginfo=info) + self.assertEqual(im.info, {"spam": "Eggs", "eggs": "Spam"}) + self.assertEqual(im.text, {"spam": "Eggs", "eggs": "Spam"}) + self.assertEqual(im.text["spam"].lang, "en") + self.assertEqual(im.text["spam"].tkey, "Spam") + self.assertEqual(im.text["eggs"].lang, "en") + self.assertEqual(im.text["eggs"].tkey, "Eggs") + def test_scary(self): # Check reading of evil PNG file. For information, see: # http://scary.beasts.org/security/CESA-2004-001.txt From a9f4e30641d9c2d0348d0de1721877998bd7cfaa Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 23 Jul 2014 07:43:52 -0700 Subject: [PATCH 413/488] Save detected non-Latin1 characters as iTXt to preserve them. --- PIL/PngImagePlugin.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 34fff1a31..4dbedb783 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -191,12 +191,15 @@ class PngInfo: return self.add_itxt(key, value, value.lang, value.tkey, bool(zip)) # The tEXt chunk stores latin-1 text + if not isinstance(value, bytes): + try: + value = value.encode('latin-1', 'strict') + except UnicodeError: + return self.add_itxt(key, value, zip=bool(zip)) + if not isinstance(key, bytes): key = key.encode('latin-1', 'strict') - if not isinstance(value, bytes): - value = value.encode('latin-1', 'replace') - if zip: import zlib self.add(b"zTXt", key + b"\0\0" + zlib.compress(value)) From 2687b5cb8deea43995aa3dc48579a273e32ea1c3 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 23 Jul 2014 08:17:11 -0700 Subject: [PATCH 414/488] Test unicode preservation in text chunks. --- Tests/test_file_png.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index fbccfb9f6..8ef166347 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -281,6 +281,34 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.text["eggs"].lang, "en") self.assertEqual(im.text["eggs"].tkey, "Eggs") + def test_nonunicode_text(self): + # Check so that non-Unicode text is saved as a tEXt rather than iTXt + + im = Image.new("RGB", (32, 32)) + info = PngImagePlugin.PngInfo() + info.add_text("Text", "Ascii") + im = roundtrip(im, pnginfo=info) + self.assertEqual(type(im.info["Text"]), str) + + def test_unicode_text(self): + # Check preservation of non-ASCII characters on Python3 + # This cannot really be meaningfully tested on Python2, + # since it didn't preserve charsets to begin with. + + def rt_text(value): + im = Image.new("RGB", (32, 32)) + info = PngImagePlugin.PngInfo() + info.add_text("Text", value) + im = roundtrip(im, pnginfo=info) + self.assertEqual(im.info, {"Text": value}) + + if str is not bytes: + rt_text(" Aa" + chr(0xa0) + chr(0xc4) + chr(0xff)) # Latin1 + rt_text(chr(0x400) + chr(0x472) + chr(0x4ff)) # Cyrillic + rt_text(chr(0x4e00) + chr(0x66f0) + # CJK + chr(0x9fba) + chr(0x3042) + chr(0xac00)) + rt_text("A" + chr(0xc4) + chr(0x472) + chr(0x3042)) # Combined + def test_scary(self): # Check reading of evil PNG file. For information, see: # http://scary.beasts.org/security/CESA-2004-001.txt From 832e11bed50d66e38e0bae5ac2ff8c9219388fc1 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Wed, 23 Jul 2014 11:27:46 -0400 Subject: [PATCH 415/488] Enabled seeking in MPO files. Note to self: either put the offset in the load_seek() definition or the tile definition, not both. --- PIL/MpoImagePlugin.py | 9 ++++++++- Tests/test_file_mpo.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 6594b74b0..6dda2e513 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -49,14 +49,21 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__frame = 0 self.offset = 0 + # for now we can only handle reading and individual frame extraction + self.readonly = 1 + + def load_seek(self, pos): + self.__fp.seek(pos) def seek(self, frame): if frame < 0 or frame >= self.__framecount: raise EOFError("no more images in MPO file") else: self.fp = self.__fp - self.fp.seek(self.__mpoffsets[frame]) self.offset = self.__mpoffsets[frame] + self.tile = [ + ("jpeg", (0, 0) + self.size, self.offset, (self.mode, "")) + ] self.__frame = frame def tell(self): diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index a0bcf6f40..b4ab38733 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -73,6 +73,20 @@ class TestFileMpo(PillowTestCase): # and this one, too im.seek(0) self.assertEqual(im.tell(), 0) + + def test_image_grab(self): + for test_file in test_files: + im = Image.open(test_file) + self.assertEqual(im.tell(), 0) + im0 = im.tobytes() + im.seek(1) + self.assertEqual(im.tell(), 1) + im1 = im.tobytes() + im.seek(0) + self.assertEqual(im.tell(), 0) + im0 = im.tobytes() + self.assertEqual(im0, im02) + self.assertNotEqual(im0, im1) if __name__ == '__main__': From 8d7266afc546ff134a59005611ad58c29f6b1c2b Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Wed, 23 Jul 2014 11:36:23 -0400 Subject: [PATCH 416/488] Fixed typo in new MPO unit test. --- Tests/test_file_mpo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index b4ab38733..9cd59bffd 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -84,7 +84,7 @@ class TestFileMpo(PillowTestCase): im1 = im.tobytes() im.seek(0) self.assertEqual(im.tell(), 0) - im0 = im.tobytes() + im02 = im.tobytes() self.assertEqual(im0, im02) self.assertNotEqual(im0, im1) From ffe8887cc6ab2d45bf366b2b039f73e7aa8a5d5f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 08:48:55 -0700 Subject: [PATCH 417/488] profiler for testing --- profile-installed.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 profile-installed.py diff --git a/profile-installed.py b/profile-installed.py new file mode 100755 index 000000000..485792f51 --- /dev/null +++ b/profile-installed.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +import nose +import os +import sys +import glob + +import profile + +# monkey with the path, removing the local directory but adding the Tests/ +# directory for helper.py and the other local imports there. + +del(sys.path[0]) +sys.path.insert(0, os.path.abspath('./Tests')) + +# if there's no test selected (mostly) choose a working default. +# Something is required, because if we import the tests from the local +# directory, once again, we've got the non-installed PIL in the way +if len(sys.argv) == 1: + sys.argv.extend(glob.glob('Tests/test*.py')) + +# Make sure that nose doesn't muck with our paths. +if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): + sys.argv.insert(1, '--no-path-adjustment') + +if 'NOSE_PROCESSES' not in os.environ: + for arg in sys.argv: + if '--processes' in arg: + break + else: # for + sys.argv.insert(1, '--processes=-1') # -1 == number of cores + sys.argv.insert(1, '--process-timeout=30') + +if __name__ == '__main__': + profile.run("nose.main()", sort=2) From 67c235b7c084c8fed6284cd1b7f43f8d67754d19 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 08:49:19 -0700 Subject: [PATCH 418/488] Don't DOS pypy --- Tests/test_format_hsv.py | 48 ++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 1a9c46bd7..be9c86e1c 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -12,7 +12,10 @@ class TestFormatHSV(PillowTestCase): return float(ord(i))/255.0 def to_int(self, f): return int(f*255.0) - + def tuple_to_ints(self, tp): + x,y,z = tp + return (int(x*255.0), int(y*255.0), int(z*255.0)) + def test_sanity(self): im = Image.new('HSV', (100,100)) @@ -49,38 +52,24 @@ class TestFormatHSV(PillowTestCase): (r,g,b) = im.split() if bytes is str: - f_r = map(self.str_to_float,r.tobytes()) - f_g = map(self.str_to_float,g.tobytes()) - f_b = map(self.str_to_float,b.tobytes()) + conv_func = self.str_to_float else: - f_r = map(self.int_to_float,r.tobytes()) - f_g = map(self.int_to_float,g.tobytes()) - f_b = map(self.int_to_float,b.tobytes()) - - f_h = []; - f_s = []; - f_v = []; + conv_func = self.int_to_float if hasattr(itertools, 'izip'): iter_helper = itertools.izip else: iter_helper = itertools.zip_longest + + + converted = [self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) + for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), b.tobytes())] + + + new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (h,s,v) in converted) + + hsv = Image.frombytes(mode,r.size, new_bytes) - for (_r, _g, _b) in iter_helper(f_r, f_g, f_b): - _h, _s, _v = func(_r, _g, _b) - f_h.append(_h) - f_s.append(_s) - f_v.append(_v) - - h = Image.new('L', r.size) - h.putdata(list(map(self.to_int, f_h))) - s = Image.new('L', r.size) - s.putdata(list(map(self.to_int, f_s))) - v = Image.new('L', r.size) - v.putdata(list(map(self.to_int, f_v))) - - hsv = Image.merge(mode, (h, s, v)) - return hsv def to_hsv_colorsys(self, im): @@ -90,8 +79,9 @@ class TestFormatHSV(PillowTestCase): return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, 'RGB') def test_wedge(self): - im = self.wedge().convert('HSV') - comparable = self.to_hsv_colorsys(self.wedge()) + src = self.wedge().resize((3*32,32),Image.BILINEAR) + im = src.convert('HSV') + comparable = self.to_hsv_colorsys(src) #print (im.getpixel((448, 64))) #print (comparable.getpixel((448, 64))) @@ -111,7 +101,7 @@ class TestFormatHSV(PillowTestCase): #print (im.getpixel((192, 64))) - comparable = self.wedge() + comparable = src im = im.convert('RGB') #im.split()[0].show() From e14e3593d98056fb86811e4613f221009320d5fe Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 09:08:28 -0700 Subject: [PATCH 419/488] And now for something completely different. Py3 compatibility --- Tests/test_format_hsv.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index be9c86e1c..03603aa9b 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -65,8 +65,10 @@ class TestFormatHSV(PillowTestCase): converted = [self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) for (_r, _g, _b) in iter_helper(r.tobytes(), g.tobytes(), b.tobytes())] - - new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (h,s,v) in converted) + if str is bytes: + new_bytes = b''.join(chr(h)+chr(s)+chr(v) for (h,s,v) in converted) + else: + new_bytes = b''.join(bytes(chr(h)+chr(s)+chr(v), 'latin-1') for (h,s,v) in converted) hsv = Image.frombytes(mode,r.size, new_bytes) From 75dac32a0a1c6755e41735c9098d4932c001e405 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 14:30:55 -0700 Subject: [PATCH 420/488] single threaded for profile-installed.py --- profile-installed.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/profile-installed.py b/profile-installed.py index 485792f51..be9a960d2 100755 --- a/profile-installed.py +++ b/profile-installed.py @@ -21,14 +21,6 @@ if len(sys.argv) == 1: # Make sure that nose doesn't muck with our paths. if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): sys.argv.insert(1, '--no-path-adjustment') - -if 'NOSE_PROCESSES' not in os.environ: - for arg in sys.argv: - if '--processes' in arg: - break - else: # for - sys.argv.insert(1, '--processes=-1') # -1 == number of cores - sys.argv.insert(1, '--process-timeout=30') if __name__ == '__main__': profile.run("nose.main()", sort=2) From 94ca2b10763bd6ed127fba85b976dff703a64f3b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 14:31:49 -0700 Subject: [PATCH 421/488] using skip known bad --- Tests/helper.py | 5 ++++- Tests/test_image_point.py | 11 ++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 082fb93f9..3f7913b11 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -124,7 +124,8 @@ class PillowTestCase(unittest.TestCase): self.assertTrue(found) return result - def skipKnownBadTest(self, msg=None, platform=None, travis=None): + def skipKnownBadTest(self, msg=None, platform=None, + travis=None, interpreter=None): # Skip if platform/travis matches, and # PILLOW_RUN_KNOWN_BAD is not true in the environment. if bool(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)): @@ -136,6 +137,8 @@ class PillowTestCase(unittest.TestCase): skip = sys.platform.startswith(platform) if travis is not None: skip = skip and (travis == bool(os.environ.get('TRAVIS', False))) + if interpreter is not None: + skip = skip and (interpreter == 'pypy' and hasattr(sys, 'pypy_version_info')) if skip: self.skipTest(msg or "Known Bad Test") diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 7b6cd4fc7..aa134fcc6 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -5,12 +5,6 @@ import sys class TestImagePoint(PillowTestCase): - def setUp(self): - if hasattr(sys, 'pypy_version_info'): - # This takes _forever_ on PyPy. Open Bug, - # see https://github.com/python-pillow/Pillow/issues/484 - self.skipTest("Too slow on PyPy") - def test_sanity(self): im = lena() @@ -29,7 +23,10 @@ class TestImagePoint(PillowTestCase): def test_16bit_lut(self): """ Tests for 16 bit -> 8 bit lut for converting I->L images see https://github.com/python-pillow/Pillow/issues/440 - """ + """ + # This takes _forever_ on PyPy. Open Bug, + # see https://github.com/python-pillow/Pillow/issues/484 + self.skipKnownBadTest(msg="Too Slow on pypy", interpreter='pypy') im = lena("I") im.point(list(range(256))*256, 'L') From a5aea42bc9d82a351d75e0e73f52f294cb48039b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 14:37:04 -0700 Subject: [PATCH 422/488] Use PySequence_Fast* to iterate over the list Pre commit: $ NOSE_PROCESSES=0 PILLOW_RUN_KNOWN_BAD=1 time ./test-installed.py Tests/test_image_point.py .. ---------------------------------------------------------------------- Ran 2 tests in 132.056s OK 131.63user 0.62system 2:12.28elapsed 99%CPU (0avgtext+0avgdata 292176maxresident)k 264inputs+0outputs (2major+451088minor)pagefaults 0swaps Post: $ NOSE_PROCESSES=0 PILLOW_RUN_KNOWN_BAD=1 time ./test-installed.py Tests/test_image_point.py .. ---------------------------------------------------------------------- Ran 2 tests in 0.338s OK 0.52user 0.06system 0:00.59elapsed 98%CPU (0avgtext+0avgdata 257584maxresident)k 176inputs+32outputs (2major+18033minor)pagefaults 0swaps $ python --version Python 2.7.6 (2.3.1+dfsg-1~ppa1, Jun 20 2014, 09:27:47) [PyPy 2.3.1 with GCC 4.6.3] --- _imaging.c | 86 +++++++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 56 deletions(-) diff --git a/_imaging.c b/_imaging.c index 92258032f..7d0bcba04 100644 --- a/_imaging.c +++ b/_imaging.c @@ -365,9 +365,12 @@ getbands(const char* mode) static void* getlist(PyObject* arg, int* length, const char* wrong_length, int type) { - int i, n; + int i, n, itemp; + double dtemp; void* list; - + PyObject* seq; + PyObject* op; + if (!PySequence_Check(arg)) { PyErr_SetString(PyExc_TypeError, must_be_sequence); return NULL; @@ -383,70 +386,41 @@ getlist(PyObject* arg, int* length, const char* wrong_length, int type) if (!list) return PyErr_NoMemory(); + seq = PySequence_Fast(arg, must_be_sequence); + if (!seq) { + free(list); + PyErr_SetString(PyExc_TypeError, must_be_sequence); + return NULL; + } + switch (type) { case TYPE_UINT8: - if (PyList_Check(arg)) { - for (i = 0; i < n; i++) { - PyObject *op = PyList_GET_ITEM(arg, i); - int temp = PyInt_AsLong(op); - ((UINT8*)list)[i] = CLIP(temp); - } - } else { - for (i = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(arg, i); - int temp = PyInt_AsLong(op); - Py_XDECREF(op); - ((UINT8*)list)[i] = CLIP(temp); - } + for (i = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + itemp = PyInt_AsLong(op); + ((UINT8*)list)[i] = CLIP(itemp); } break; case TYPE_INT32: - if (PyList_Check(arg)) { - for (i = 0; i < n; i++) { - PyObject *op = PyList_GET_ITEM(arg, i); - int temp = PyInt_AsLong(op); - ((INT32*)list)[i] = temp; - } - } else { - for (i = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(arg, i); - int temp = PyInt_AsLong(op); - Py_XDECREF(op); - ((INT32*)list)[i] = temp; - } + for (i = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + itemp = PyInt_AsLong(op); + ((INT32*)list)[i] = itemp; } break; case TYPE_FLOAT32: - if (PyList_Check(arg)) { - for (i = 0; i < n; i++) { - PyObject *op = PyList_GET_ITEM(arg, i); - double temp = PyFloat_AsDouble(op); - ((FLOAT32*)list)[i] = (FLOAT32) temp; - } - } else { - for (i = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(arg, i); - double temp = PyFloat_AsDouble(op); - Py_XDECREF(op); - ((FLOAT32*)list)[i] = (FLOAT32) temp; - } - } + for (i = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + dtemp = PyFloat_AsDouble(op); + ((FLOAT32*)list)[i] = (FLOAT32) dtemp; + } break; case TYPE_DOUBLE: - if (PyList_Check(arg)) { - for (i = 0; i < n; i++) { - PyObject *op = PyList_GET_ITEM(arg, i); - double temp = PyFloat_AsDouble(op); - ((double*)list)[i] = temp; - } - } else { - for (i = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(arg, i); - double temp = PyFloat_AsDouble(op); - Py_XDECREF(op); - ((double*)list)[i] = temp; - } - } + for (i = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + dtemp = PyFloat_AsDouble(op); + ((double*)list)[i] = (double) dtemp; + } break; } From 5def1010c72bde10cebc2aafbb7cf6a30ae070c7 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 15:16:23 -0700 Subject: [PATCH 423/488] DRY, moved case inside loop --- _imaging.c | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/_imaging.c b/_imaging.c index 7d0bcba04..7c55f633b 100644 --- a/_imaging.c +++ b/_imaging.c @@ -393,35 +393,28 @@ getlist(PyObject* arg, int* length, const char* wrong_length, int type) return NULL; } - switch (type) { - case TYPE_UINT8: - for (i = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); + for (i = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + // DRY, branch prediction is going to work _really_ well + // on this switch. And 3 fewer loops to copy/paste. + switch (type) { + case TYPE_UINT8: itemp = PyInt_AsLong(op); ((UINT8*)list)[i] = CLIP(itemp); - } - break; - case TYPE_INT32: - for (i = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); + break; + case TYPE_INT32: itemp = PyInt_AsLong(op); ((INT32*)list)[i] = itemp; - } - break; - case TYPE_FLOAT32: - for (i = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); - dtemp = PyFloat_AsDouble(op); - ((FLOAT32*)list)[i] = (FLOAT32) dtemp; - } - break; - case TYPE_DOUBLE: - for (i = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); + break; + case TYPE_FLOAT32: + dtemp = PyFloat_AsDouble(op); + ((FLOAT32*)list)[i] = (FLOAT32) dtemp; + break; + case TYPE_DOUBLE: dtemp = PyFloat_AsDouble(op); ((double*)list)[i] = (double) dtemp; - } - break; + break; + } } if (length) From 06d21bc709e80ffb97429ed85dde5530d3888526 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 15:29:10 -0700 Subject: [PATCH 424/488] pypy performance test --- Tests/test_image_putdata.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index c7c3115aa..036ee285f 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -42,6 +42,10 @@ class TestImagePutData(PillowTestCase): self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) + def test_pypy_performance(self): + im = Image.new('L', (256,256)) + im.putdata(list(range(256))*256) + if __name__ == '__main__': unittest.main() From c9c80f9da5fe120181deb970e43efef903156bda Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 15:38:11 -0700 Subject: [PATCH 425/488] Use PySequence_Fast for Image.putdata Pre-Commit: $ NOSE_PROCESSES=0 time ./test-installed.py Tests/test_image_putdata.py ... ---------------------------------------------------------------------- Ran 3 tests in 131.623s OK 131.77user 0.18system 2:14.04elapsed 98%CPU (0avgtext+0avgdata 325632maxresident)k 87376inputs+8outputs (314major+47333minor)pagefaults 0swaps Post: $ NOSE_PROCESSES=0 time ./test-installed.py Tests/test_image_putdata.py ... ---------------------------------------------------------------------- Ran 3 tests in 0.534s OK 0.77user 0.05system 0:00.83elapsed 99%CPU (0avgtext+0avgdata 296352maxresident)k 0inputs+0outputs (0major+21462minor)pagefaults 0swaps --- _imaging.c | 91 +++++++++++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/_imaging.c b/_imaging.c index 7c55f633b..c28bd4d93 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1220,6 +1220,8 @@ _putdata(ImagingObject* self, PyObject* args) Py_ssize_t n, i, x, y; PyObject* data; + PyObject* seq; + PyObject* op; double scale = 1.0; double offset = 0.0; @@ -1259,69 +1261,61 @@ _putdata(ImagingObject* self, PyObject* args) x = 0, y++; } } else { - if (scale == 1.0 && offset == 0.0) { - /* Clipped data */ - if (PyList_Check(data)) { - for (i = x = y = 0; i < n; i++) { - PyObject *op = PyList_GET_ITEM(data, i); - image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); - if (++x >= (int) image->xsize) - x = 0, y++; - } - } else { - for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(data, i); - image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); - Py_XDECREF(op); - if (++x >= (int) image->xsize) - x = 0, y++; - } - } + seq = PySequence_Fast(data, must_be_sequence); + if (!seq) { + PyErr_SetString(PyExc_TypeError, must_be_sequence); + return NULL; + } + if (scale == 1.0 && offset == 0.0) { + /* Clipped data */ + for (i = x = y = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(data, i); + image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); + if (++x >= (int) image->xsize){ + x = 0, y++; + } + } + } else { - if (PyList_Check(data)) { - /* Scaled and clipped data */ - for (i = x = y = 0; i < n; i++) { - PyObject *op = PyList_GET_ITEM(data, i); - image->image8[y][x] = CLIP( - (int) (PyFloat_AsDouble(op) * scale + offset)); - if (++x >= (int) image->xsize) - x = 0, y++; - } - } else { - for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(data, i); - image->image8[y][x] = CLIP( - (int) (PyFloat_AsDouble(op) * scale + offset)); - Py_XDECREF(op); - if (++x >= (int) image->xsize) - x = 0, y++; - } - } - } - PyErr_Clear(); /* Avoid weird exceptions */ + /* Scaled and clipped data */ + for (i = x = y = 0; i < n; i++) { + PyObject *op = PySequence_Fast_GET_ITEM(data, i); + image->image8[y][x] = CLIP( + (int) (PyFloat_AsDouble(op) * scale + offset)); + if (++x >= (int) image->xsize){ + x = 0, y++; + } + } + } + PyErr_Clear(); /* Avoid weird exceptions */ } } else { /* 32-bit images */ + seq = PySequence_Fast(data, must_be_sequence); + if (!seq) { + PyErr_SetString(PyExc_TypeError, must_be_sequence); + return NULL; + } switch (image->type) { case IMAGING_TYPE_INT32: for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(data, i); + op = PySequence_Fast_GET_ITEM(data, i); IMAGING_PIXEL_INT32(image, x, y) = (INT32) (PyFloat_AsDouble(op) * scale + offset); - Py_XDECREF(op); - if (++x >= (int) image->xsize) + if (++x >= (int) image->xsize){ x = 0, y++; + } } PyErr_Clear(); /* Avoid weird exceptions */ break; case IMAGING_TYPE_FLOAT32: for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_GetItem(data, i); + op = PySequence_Fast_GET_ITEM(data, i); IMAGING_PIXEL_FLOAT32(image, x, y) = (FLOAT32) (PyFloat_AsDouble(op) * scale + offset); - Py_XDECREF(op); - if (++x >= (int) image->xsize) + if (++x >= (int) image->xsize){ x = 0, y++; + } } PyErr_Clear(); /* Avoid weird exceptions */ break; @@ -1332,16 +1326,15 @@ _putdata(ImagingObject* self, PyObject* args) INT32 inkint; } u; - PyObject *op = PySequence_GetItem(data, i); + op = PySequence_Fast_GET_ITEM(data, i); if (!op || !getink(op, image, u.ink)) { - Py_DECREF(op); return NULL; } /* FIXME: what about scale and offset? */ image->image32[y][x] = u.inkint; - Py_XDECREF(op); - if (++x >= (int) image->xsize) + if (++x >= (int) image->xsize){ x = 0, y++; + } } PyErr_Clear(); /* Avoid weird exceptions */ break; From 2d13dbda6a3d9ddc8ac93e839162353bb7d9811d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 16:01:06 -0700 Subject: [PATCH 426/488] enable test_16bit_lut on pypy --- Tests/test_image_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index aa134fcc6..63fb6fc38 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -26,7 +26,7 @@ class TestImagePoint(PillowTestCase): """ # This takes _forever_ on PyPy. Open Bug, # see https://github.com/python-pillow/Pillow/issues/484 - self.skipKnownBadTest(msg="Too Slow on pypy", interpreter='pypy') + #self.skipKnownBadTest(msg="Too Slow on pypy", interpreter='pypy') im = lena("I") im.point(list(range(256))*256, 'L') From ea0a31d9fe9b4088423bebb3ba259098268e2102 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Jul 2014 17:02:57 -0700 Subject: [PATCH 427/488] 2.8 million pyaccesses take a while, nomatter what --- Tests/test_mode_i16.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index b7dc76fb4..0cd5dba0f 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -5,18 +5,22 @@ from PIL import Image class TestModeI16(PillowTestCase): + original = lena().resize((32,32)).convert('I') + def verify(self, im1): - im2 = lena("I") + im2 = self.original.copy() self.assertEqual(im1.size, im2.size) pix1 = im1.load() pix2 = im2.load() for y in range(im1.size[1]): for x in range(im1.size[0]): xy = x, y + p1 = pix1[xy] + p2 = pix2[xy] self.assertEqual( - pix1[xy], pix2[xy], + p1, p2, ("got %r from mode %s at %s, expected %r" % - (pix1[xy], im1.mode, xy, pix2[xy]))) + (p1, im1.mode, xy, p2))) def test_basic(self): # PIL 1.1 has limited support for 16-bit image data. Check that @@ -24,7 +28,7 @@ class TestModeI16(PillowTestCase): def basic(mode): - imIn = lena("I").convert(mode) + imIn = self.original.convert(mode) self.verify(imIn) w, h = imIn.size @@ -92,7 +96,7 @@ class TestModeI16(PillowTestCase): def test_convert(self): - im = lena("I") + im = self.original.copy() self.verify(im.convert("I;16")) self.verify(im.convert("I;16").convert("L")) From 13bd1d6006cf93c0b3f016a3efd502c8dcef3b95 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 24 Jul 2014 09:21:09 +0300 Subject: [PATCH 428/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c93572cbd..a0253def0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- HSV Support #816 + [wiredfool] + - Removed unusable ImagePalette.new() [hugovk] From dcd171c1b8485eb4234344194727e55ea090b85f Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Thu, 24 Jul 2014 11:16:12 -0400 Subject: [PATCH 429/488] Minor refactoring per discussion and MPO docs. --- PIL/Image.py | 13 ------------- PIL/JpegImagePlugin.py | 19 ++++++++++++++++++- PIL/MpoImagePlugin.py | 3 ++- docs/handbook/image-file-formats.rst | 14 ++++++++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index eb08fab9e..3a42e308b 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2236,19 +2236,6 @@ def open(fp, mode="r"): fp.seek(0) im = factory(fp, filename) _decompression_bomb_check(im.size) - if i == 'JPEG': - # Things are more complicated for JPEGs; we need to parse - # more deeply than 16 characters and check the contents of - # a potential MP header block to be sure. - mpheader = im._getmp() - try: - if mpheader[45057] > 1: - # It's actually an MPO - factory, accept = OPEN['MPO'] - im = factory(fp, filename) - except (TypeError, IndexError): - # It is really a JPEG - pass return im except (SyntaxError, IndexError, TypeError): # import traceback diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index bedc42373..fbb855362 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -663,10 +663,27 @@ def _save_cjpeg(im, fp, filename): except: pass + +## +# Factory for making JPEG and MPO instances +def jpeg_factory(fp=None, filename=None): + im = JpegImageFile(fp, filename) + mpheader = im._getmp() + try: + if mpheader[45057] > 1: + # It's actually an MPO + from MpoImagePlugin import MpoImageFile + im = MpoImageFile(fp, filename) + except (TypeError, IndexError): + # It is really a JPEG + pass + return im + + # -------------------------------------------------------------------q- # Registry stuff -Image.register_open("JPEG", JpegImageFile, _accept) +Image.register_open("JPEG", jpeg_factory, _accept) Image.register_save("JPEG", _save) Image.register_extension("JPEG", ".jfif") diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 6dda2e513..3c5bc35d4 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -37,6 +37,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): format_description = "MPO (CIPA DC-007)" def _open(self): + self.fp.seek(0) # prep the fp in order to pass the JPEG test JpegImagePlugin.JpegImageFile._open(self) self.mpinfo = self._getmp() self.__framecount = self.mpinfo[0xB001] @@ -73,7 +74,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): # -------------------------------------------------------------------q- # Registry stuff -Image.register_open("MPO", MpoImageFile, _accept) +Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept) Image.register_save("MPO", _save) Image.register_extension("MPO", ".mpo") diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 03e55f35a..bfc94058a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -588,6 +588,20 @@ PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the first sprite in the file is loaded. You can use :py:meth:`~file.seek` and :py:meth:`~file.tell` to read other sprites from the file. +MPO +^^^ + +PIL identifies and reads Multi Picture Object (MPO) files, loading the primary +image when first opened. The :py:meth:`~file.seek` and :py:meth:`~file.tell` +methods may be used to read other pictures from the file. The pictures are +zero-indexed and random access is supported. + +MIC (read only) + +PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the +first sprite in the file is loaded. You can use :py:meth:`~file.seek` and +:py:meth:`~file.tell` to read other sprites from the file. + PCD ^^^ From a5683ab574ef6a356445b7a29c5dc2da2a1abc6f Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Thu, 24 Jul 2014 15:00:19 -0400 Subject: [PATCH 430/488] Implemented MP attribute breakdown with tests. --- PIL/JpegImagePlugin.py | 27 +++++++++++++++++++++++++++ PIL/TiffTags.py | 14 ++++++++++++++ Tests/test_file_mpo.py | 25 ++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index fbb855362..511208267 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -474,10 +474,37 @@ def _getmp(self): unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry) labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2') mpentry = dict(zip(labels, unpackedentry)) + mpentryattr = { + 'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)), + 'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)), + 'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)), + 'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27, + 'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24, + 'MPType': mpentry['Attribute'] & 0x00FFFFFF + } + if mpentryattr['ImageDataFormat'] == 0: + mpentryattr['ImageDataFormat'] = 'JPEG' + else: + raise SyntaxError("unsupported picture format in MPO") + mptypemap = { + 0x000000: 'Undefined', + 0x010001: 'Large Thumbnail (VGA Equivalent)', + 0x010002: 'Large Thumbnail (Full HD Equivalent)', + 0x020001: 'Multi-Frame Image (Panorama)', + 0x020002: 'Multi-Frame Image: (Disparity)', + 0x020003: 'Multi-Frame Image: (Multi-Angle)', + 0x030000: 'Baseline MP Primary Image' + } + mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'], + 'Unknown') + mpentry['Attribute'] = mpentryattr mpentries.append(mpentry) mp[0xB002] = mpentries except KeyError: raise SyntaxError("malformed MP Index (bad MP Entry)") + # Next we should try and parse the individual image unique ID list; + # we don't because I've never seen this actually used in a real MPO + # file and so can't test it. return mp diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index f4420aacc..966779ce9 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -226,6 +226,20 @@ TAGS = { 45058: "MPEntry", 45059: "ImageUIDList", 45060: "TotalFrames", + 45313: "MPIndividualNum", + 45569: "PanOrientation", + 45570: "PanOverlap_H", + 45571: "PanOverlap_V", + 45572: "BaseViewpointNum", + 45573: "ConvergenceAngle", + 45574: "BaselineLength", + 45575: "VerticalDivergence", + 45576: "AxisDistance_X", + 45577: "AxisDistance_Y", + 45578: "AxisDistance_Z", + 45579: "YawAngle", + 45580: "PitchAngle", + 45581: "RollAngle", # Adobe DNG 50706: "DNGVersion", diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 9cd59bffd..3ae26ff91 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -51,10 +51,29 @@ class TestFileMpo(PillowTestCase): def test_mp(self): for test_file in test_files: im = Image.open(test_file) - info = im._getmp() - self.assertEqual(info[45056], '0100') - self.assertEqual(info[45057], 2) + mpinfo = im._getmp() + self.assertEqual(mpinfo[45056], '0100') + self.assertEqual(mpinfo[45057], 2) + def test_mp_attribute(self): + for test_file in test_files: + im = Image.open(test_file) + mpinfo = im._getmp() + frameNumber = 0 + for mpentry in mpinfo[45058]: + mpattr = mpentry['Attribute'] + if frameNumber: + self.assertFalse(mpattr['RepresentativeImageFlag']) + else: + self.assertTrue(mpattr['RepresentativeImageFlag']) + self.assertFalse(mpattr['DependentParentImageFlag']) + self.assertFalse(mpattr['DependentChildImageFlag']) + self.assertEqual(mpattr['ImageDataFormat'], 'JPEG') + self.assertEqual(mpattr['MPType'], + 'Multi-Frame Image: (Disparity)') + self.assertEqual(mpattr['Reserved'], 0) + frameNumber += 1 + def test_seek(self): for test_file in test_files: im = Image.open(test_file) From 14976346a874ba4765a0af660ba1a01c1ee4232d Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Fri, 25 Jul 2014 11:50:21 -0400 Subject: [PATCH 431/488] Fixed import for Python 3. Fixed the trivial import bug that prevented the Python 3 version of MPO from running. On the way fixed the trivial C bug that prevented Convert.c from compiling properly in a Mac OS X environment for a Python 3 target. --- PIL/JpegImagePlugin.py | 2 +- libImaging/Convert.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 511208267..9cbab6b61 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -699,7 +699,7 @@ def jpeg_factory(fp=None, filename=None): try: if mpheader[45057] > 1: # It's actually an MPO - from MpoImagePlugin import MpoImageFile + from .MpoImagePlugin import MpoImageFile im = MpoImageFile(fp, filename) except (TypeError, IndexError): # It is really a JPEG diff --git a/libImaging/Convert.c b/libImaging/Convert.c index 4eb106c27..607441191 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -49,6 +49,10 @@ #define L(rgb)\ ((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114) +#ifndef uint +#define uint UINT32 +#endif + /* ------------------- */ /* 1 (bit) conversions */ /* ------------------- */ From 023ec0a2fc3d92fc12c1eeb60a8539662582add6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 25 Jul 2014 10:32:55 -0700 Subject: [PATCH 432/488] Incorrect type -- fails on OSX --- libImaging/Convert.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libImaging/Convert.c b/libImaging/Convert.c index 4eb106c27..46a6cfb72 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -293,10 +293,10 @@ hsv2rgb(UINT8* out, const UINT8* in, int xsize) { // following colorsys.py int p,q,t; - uint up,uq,ut; + UINT8 up,uq,ut; int i, x; float f, fs; - uint h,s,v; + UINT8 h,s,v; for (x = 0; x < xsize; x++, in += 4) { h = in[0]; From 3b3f58d1bcaba0a4fd346ff65d679693480c7f32 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Fri, 25 Jul 2014 14:47:07 -0400 Subject: [PATCH 433/488] Changed PIL to Pillow for newly supported formats. --- Tests/test_file_mpo.py | 2 +- docs/handbook/image-file-formats.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 3ae26ff91..20589539b 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -52,7 +52,7 @@ class TestFileMpo(PillowTestCase): for test_file in test_files: im = Image.open(test_file) mpinfo = im._getmp() - self.assertEqual(mpinfo[45056], '0100') + self.assertEqual(mpinfo[45056], b'0100') self.assertEqual(mpinfo[45057], 2) def test_mp_attribute(self): diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index bfc94058a..8c9bb36ec 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -591,14 +591,14 @@ first sprite in the file is loaded. You can use :py:meth:`~file.seek` and MPO ^^^ -PIL identifies and reads Multi Picture Object (MPO) files, loading the primary +Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary image when first opened. The :py:meth:`~file.seek` and :py:meth:`~file.tell` methods may be used to read other pictures from the file. The pictures are zero-indexed and random access is supported. MIC (read only) -PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the +Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened, the first sprite in the file is loaded. You can use :py:meth:`~file.seek` and :py:meth:`~file.tell` to read other sprites from the file. From d54fe7fa1abe85e340a673e4f8f8873e80f98daf Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Fri, 25 Jul 2014 16:55:50 -0400 Subject: [PATCH 434/488] Removed unnecessary uint definition. --- libImaging/Convert.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libImaging/Convert.c b/libImaging/Convert.c index 10c4c104a..46a6cfb72 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -49,10 +49,6 @@ #define L(rgb)\ ((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114) -#ifndef uint -#define uint UINT32 -#endif - /* ------------------- */ /* 1 (bit) conversions */ /* ------------------- */ From f234264959a93e9697f0eab7fabcd672c74ad287 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 26 Jul 2014 10:31:15 +0300 Subject: [PATCH 435/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a0253def0..16f8234c7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Added support for encoding and decoding iTXt chunks #818 + [dolda2000] + - HSV Support #816 [wiredfool] From 0dabb8ea1c1c66388a020025a076a531e3219f70 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 26 Jul 2014 09:59:33 -0700 Subject: [PATCH 436/488] Image Mode Docs --- docs/handbook/concepts.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index 93f964e41..b5e5e44c1 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -27,6 +27,8 @@ image. The current release supports the following standard modes: * ``RGBA`` (4x8-bit pixels, true color with transparency mask) * ``CMYK`` (4x8-bit pixels, color separation) * ``YCbCr`` (3x8-bit pixels, color video format) + * ``LAB`` (3x8-bit pixels, the L*a*b color space) + * ``HSV`` (3x8-bit pixels, Hue, Saturation, Value color space) * ``I`` (32-bit signed integer pixels) * ``F`` (32-bit floating point pixels) @@ -34,7 +36,7 @@ PIL also provides limited support for a few special modes, including ``LA`` (L with alpha), ``RGBX`` (true color with padding) and ``RGBa`` (true color with premultiplied alpha). However, PIL doesn’t support user-defined modes; if you to handle band combinations that are not listed above, use a sequence of Image -objects. +objects. You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode` attribute. This is a string containing one of the above values. From a63901f028ebe1b69d85a6e12ef095f70d94fb9d Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 27 Jul 2014 13:14:28 +0300 Subject: [PATCH 437/488] Add CONTRIBUTING.md [CI skip] --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..e6f4c3f9d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. + +Let us know: + + * What is the expected output? What do you see instead? + + * What versions of Pillow and Python are you using? From 659b8c2f6f7ed8aceb011cb88ef5ba0fbd1e5a08 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 27 Jul 2014 22:18:42 +0300 Subject: [PATCH 438/488] More tests for TiffImagePlugin.py --- Tests/test_file_tiff.py | 131 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 72156bb39..9c832c206 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -141,6 +141,137 @@ class TestFileTiff(PillowTestCase): self.assertEqual( im.getextrema(), (-3.140936851501465, 3.140684127807617)) + def test___str__(self): + # Arrange + file = "Tests/images/pil136.tiff" + im = Image.open(file) + + # Act + ret = str(im.ifd) + + # Assert + self.assertIsInstance(ret, str) + self.assertEqual( + ret, + '{256: (55,), 257: (43,), 258: (8, 8, 8, 8), 259: (1,), ' + '262: (2,), 296: (2,), 273: (8,), 338: (1,), 277: (4,), ' + '279: (9460,), 282: ((720000, 10000),), ' + '283: ((720000, 10000),), 284: (1,)}') + + def test__delitem__(self): + # Arrange + file = "Tests/images/pil136.tiff" + im = Image.open(file) + len_before = len(im.ifd.as_dict()) + + # Act + del im.ifd[256] + + # Assert + len_after = len(im.ifd.as_dict()) + self.assertEqual(len_before, len_after + 1) + + def test_load_byte(self): + # Arrange + from PIL import TiffImagePlugin + ifd = TiffImagePlugin.ImageFileDirectory() + data = b"abc" + + # Act + ret = ifd.load_byte(data) + + # Assert + self.assertEqual(ret, b"abc") + + def test_load_string(self): + # Arrange + from PIL import TiffImagePlugin + ifd = TiffImagePlugin.ImageFileDirectory() + data = b"abc\0" + + # Act + ret = ifd.load_string(data) + + # Assert + self.assertEqual(ret, "abc") + + def test_load_float(self): + # Arrange + from PIL import TiffImagePlugin + ifd = TiffImagePlugin.ImageFileDirectory() + data = b"abcdabcd" + + # Act + ret = ifd.load_float(data) + + # Assert + self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22)) + + def test_load_double(self): + # Arrange + from PIL import TiffImagePlugin + ifd = TiffImagePlugin.ImageFileDirectory() + data = b"abcdefghabcdefgh" + + # Act + ret = ifd.load_double(data) + + # Assert + self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194)) + + def test_seek(self): + # Arrange + file = "Tests/images/pil136.tiff" + im = Image.open(file) + + # Act + im.seek(-1) + + # Assert + self.assertEqual(im.tell(), 0) + + def test_seek_eof(self): + # Arrange + file = "Tests/images/pil136.tiff" + im = Image.open(file) + self.assertEqual(im.tell(), 0) + + # Act / Assert + self.assertRaises(EOFError, lambda: im.seek(1)) + + def test__cvt_res_int(self): + # Arrange + from PIL.TiffImagePlugin import _cvt_res + value = 34 + + # Act + ret = _cvt_res(value) + + # Assert + self.assertEqual(ret, (34, 1)) + + def test__cvt_res_float(self): + # Arrange + from PIL.TiffImagePlugin import _cvt_res + value = 22.3 + + # Act + ret = _cvt_res(value) + + # Assert + self.assertEqual(ret, (1461452, 65536)) + + def test__cvt_res_sequence(self): + # Arrange + from PIL.TiffImagePlugin import _cvt_res + value = [0, 1] + + # Act + ret = _cvt_res(value) + + # Assert + self.assertEqual(ret, [0, 1]) + if __name__ == '__main__': unittest.main() From c97d1a5601bd5695eeaca31b82c7bc97b1ce8692 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Mon, 28 Jul 2014 11:14:38 -0400 Subject: [PATCH 439/488] Updates to MPO handler based on review. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Took out the explicit reference to the MPO Plug-in in Image as it’s now indirectly referenced via the JPEG Plug-in. Removed the direct MPO Plug-in registration as it’s now shared with the JPEG Plug-in. Commented on assertion. --- PIL/Image.py | 4 ---- PIL/MpoImagePlugin.py | 6 +++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 489b59b92..480410eff 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -353,10 +353,6 @@ def preinit(): from PIL import JpegImagePlugin except ImportError: pass - try: - from PIL import MpoImagePlugin - except ImportError: - pass try: from PIL import PpmImagePlugin except ImportError: diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 3c5bc35d4..520e683d4 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -44,6 +44,8 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \ for mpent in self.mpinfo[0xB002]] self.__mpoffsets[0] = 0 + # Note that the following assertion will only be invalid if something + # gets broken within JpegImagePlugin. assert self.__framecount == len(self.__mpoffsets) del self.info['mpoffset'] # no longer needed self.__fp = self.fp # FIXME: hack @@ -74,7 +76,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): # -------------------------------------------------------------------q- # Registry stuff -Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept) +# Note that since MPO shares a factory with JPEG, we do not need to do a +# separate registration for it here. +#Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept) Image.register_save("MPO", _save) Image.register_extension("MPO", ".mpo") From 70528dd539fecb578a4f671015863351133c5820 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 28 Jul 2014 19:00:06 +0300 Subject: [PATCH 440/488] flake8 --- PIL/TiffImagePlugin.py | 228 +++++++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 101 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 2e49931f7..faa7ff6cf 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -49,17 +49,18 @@ from PIL import _binary from PIL._util import isStringType import warnings -import array, sys +import array +import sys import collections import itertools import os # Set these to true to force use of libtiff for reading or writing. READ_LIBTIFF = False -WRITE_LIBTIFF= False +WRITE_LIBTIFF = False -II = b"II" # little-endian (intel-style) -MM = b"MM" # big-endian (motorola-style) +II = b"II" # little-endian (Intel style) +MM = b"MM" # big-endian (Motorola style) i8 = _binary.i8 o8 = _binary.o8 @@ -109,8 +110,8 @@ EXTRASAMPLES = 338 SAMPLEFORMAT = 339 JPEGTABLES = 347 COPYRIGHT = 33432 -IPTC_NAA_CHUNK = 33723 # newsphoto properties -PHOTOSHOP_CHUNK = 34377 # photoshop properties +IPTC_NAA_CHUNK = 33723 # newsphoto properties +PHOTOSHOP_CHUNK = 34377 # photoshop properties ICCPROFILE = 34675 EXIFIFD = 34665 XMP = 700 @@ -126,10 +127,10 @@ COMPRESSION_INFO = { 3: "group3", 4: "group4", 5: "tiff_lzw", - 6: "tiff_jpeg", # obsolete + 6: "tiff_jpeg", # obsolete 7: "jpeg", 8: "tiff_adobe_deflate", - 32771: "tiff_raw_16", # 16-bit padding + 32771: "tiff_raw_16", # 16-bit padding 32773: "packbits", 32809: "tiff_thunderscan", 32946: "tiff_deflate", @@ -137,7 +138,7 @@ COMPRESSION_INFO = { 34677: "tiff_sgilog24", } -COMPRESSION_INFO_REV = dict([(v,k) for (k,v) in COMPRESSION_INFO.items()]) +COMPRESSION_INFO_REV = dict([(v, k) for (k, v) in COMPRESSION_INFO.items()]) OPEN_INFO = { # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, @@ -150,7 +151,7 @@ OPEN_INFO = { (II, 1, 1, 1, (1,), ()): ("1", "1"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 1, (8,), ()): ("L", "L"), - (II, 1, 1, 1, (8,8), (2,)): ("LA", "LA"), + (II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"), (II, 1, 1, 2, (8,), ()): ("L", "L;R"), (II, 1, 1, 1, (12,), ()): ("I;16", "I;12"), (II, 1, 1, 1, (16,), ()): ("I;16", "I;16"), @@ -158,13 +159,13 @@ OPEN_INFO = { (II, 1, 1, 1, (32,), ()): ("I", "I;32N"), (II, 1, 2, 1, (32,), ()): ("I", "I;32S"), (II, 1, 3, 1, (32,), ()): ("F", "F;32F"), - (II, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), - (II, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), - (II, 2, 1, 1, (8,8,8,8), ()): ("RGBA", "RGBA"), # missing ExtraSamples - (II, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), - (II, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), - (II, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), - (II, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 + (II, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"), + (II, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"), + (II, 2, 1, 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples + (II, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"), + (II, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), + (II, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), + (II, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (II, 3, 1, 1, (1,), ()): ("P", "P;1"), (II, 3, 1, 2, (1,), ()): ("P", "P;1R"), (II, 3, 1, 1, (2,), ()): ("P", "P;2"), @@ -172,11 +173,11 @@ OPEN_INFO = { (II, 3, 1, 1, (4,), ()): ("P", "P;4"), (II, 3, 1, 2, (4,), ()): ("P", "P;4R"), (II, 3, 1, 1, (8,), ()): ("P", "P"), - (II, 3, 1, 1, (8,8), (2,)): ("PA", "PA"), + (II, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"), (II, 3, 1, 2, (8,), ()): ("P", "P;R"), - (II, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), - (II, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), - (II, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"), + (II, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), + (II, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), + (II, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"), (MM, 0, 1, 1, (1,), ()): ("1", "1;I"), (MM, 0, 1, 2, (1,), ()): ("1", "1;IR"), @@ -185,18 +186,18 @@ OPEN_INFO = { (MM, 1, 1, 1, (1,), ()): ("1", "1"), (MM, 1, 1, 2, (1,), ()): ("1", "1;R"), (MM, 1, 1, 1, (8,), ()): ("L", "L"), - (MM, 1, 1, 1, (8,8), (2,)): ("LA", "LA"), + (MM, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"), (MM, 1, 1, 2, (8,), ()): ("L", "L;R"), (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"), (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"), (MM, 1, 2, 1, (32,), ()): ("I;32BS", "I;32BS"), (MM, 1, 3, 1, (32,), ()): ("F", "F;32BF"), - (MM, 2, 1, 1, (8,8,8), ()): ("RGB", "RGB"), - (MM, 2, 1, 2, (8,8,8), ()): ("RGB", "RGB;R"), - (MM, 2, 1, 1, (8,8,8,8), (0,)): ("RGBX", "RGBX"), - (MM, 2, 1, 1, (8,8,8,8), (1,)): ("RGBA", "RGBa"), - (MM, 2, 1, 1, (8,8,8,8), (2,)): ("RGBA", "RGBA"), - (MM, 2, 1, 1, (8,8,8,8), (999,)): ("RGBA", "RGBA"), # corel draw 10 + (MM, 2, 1, 1, (8, 8, 8), ()): ("RGB", "RGB"), + (MM, 2, 1, 2, (8, 8, 8), ()): ("RGB", "RGB;R"), + (MM, 2, 1, 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"), + (MM, 2, 1, 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), + (MM, 2, 1, 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), + (MM, 2, 1, 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (MM, 3, 1, 1, (1,), ()): ("P", "P;1"), (MM, 3, 1, 2, (1,), ()): ("P", "P;1R"), (MM, 3, 1, 1, (2,), ()): ("P", "P;2"), @@ -204,19 +205,21 @@ OPEN_INFO = { (MM, 3, 1, 1, (4,), ()): ("P", "P;4"), (MM, 3, 1, 2, (4,), ()): ("P", "P;4R"), (MM, 3, 1, 1, (8,), ()): ("P", "P"), - (MM, 3, 1, 1, (8,8), (2,)): ("PA", "PA"), + (MM, 3, 1, 1, (8, 8), (2,)): ("PA", "PA"), (MM, 3, 1, 2, (8,), ()): ("P", "P;R"), - (MM, 5, 1, 1, (8,8,8,8), ()): ("CMYK", "CMYK"), - (MM, 6, 1, 1, (8,8,8), ()): ("YCbCr", "YCbCr"), - (MM, 8, 1, 1, (8,8,8), ()): ("LAB", "LAB"), + (MM, 5, 1, 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), + (MM, 6, 1, 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), + (MM, 8, 1, 1, (8, 8, 8), ()): ("LAB", "LAB"), } PREFIXES = [b"MM\000\052", b"II\052\000", b"II\xBC\000"] + def _accept(prefix): return prefix[:4] in PREFIXES + ## # Wrapper for TIFF IFDs. @@ -276,7 +279,7 @@ class ImageFileDirectory(collections.MutableMapping): #: For a complete dictionary, use the as_dict method. self.tags = {} self.tagdata = {} - self.tagtype = {} # added 2008-06-05 by Florian Hoech + self.tagtype = {} # added 2008-06-05 by Florian Hoech self.next = None def __str__(self): @@ -287,7 +290,9 @@ class ImageFileDirectory(collections.MutableMapping): return dict(self.items()) def named(self): - """Returns the complete tag dictionary, with named tags where posible.""" + """ + Returns the complete tag dictionary, with named tags where posible. + """ from PIL import TiffTags result = {} for tag_code, value in self.items(): @@ -295,7 +300,6 @@ class ImageFileDirectory(collections.MutableMapping): result[tag_name] = value return result - # dictionary API def __len__(self): @@ -305,7 +309,7 @@ class ImageFileDirectory(collections.MutableMapping): try: return self.tags[tag] except KeyError: - data = self.tagdata[tag] # unpack on the fly + data = self.tagdata[tag] # unpack on the fly type = self.tagtype[tag] size, handler = self.load_dispatch[type] self.tags[tag] = data = handler(self, data) @@ -319,7 +323,7 @@ class ImageFileDirectory(collections.MutableMapping): if tag == SAMPLEFORMAT: # work around broken (?) matrox library # (from Ted Wright, via Bob Klimek) - raise KeyError # use default + raise KeyError # use default raise ValueError("not a scalar") return value[0] except KeyError: @@ -433,7 +437,7 @@ class ImageFileDirectory(collections.MutableMapping): except KeyError: if Image.DEBUG: print("- unsupported type", typ) - continue # ignore unsupported type + continue # ignore unsupported type size, handler = dispatch @@ -449,14 +453,19 @@ class ImageFileDirectory(collections.MutableMapping): data = ifd[8:8+size] if len(data) != size: - warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag)) + warnings.warn( + "Possibly corrupt EXIF data. " + "Expecting to read %d bytes but only got %d. " + "Skipping tag %s" % (size, len(data), tag)) continue self.tagdata[tag] = data self.tagtype[tag] = typ if Image.DEBUG: - if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): + if tag in ( + COLORMAP, IPTC_NAA_CHUNK, + PHOTOSHOP_CHUNK, ICCPROFILE, XMP): print("- value: " % size) else: print("- value:", self[tag]) @@ -518,8 +527,8 @@ class ImageFileDirectory(collections.MutableMapping): # integer data if tag == STRIPOFFSETS: stripoffsets = len(directory) - typ = 4 # to avoid catch-22 - elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5: + typ = 4 # to avoid catch-22 + elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5: # identify rational data fields typ = 5 if isinstance(value[0], tuple): @@ -541,7 +550,9 @@ class ImageFileDirectory(collections.MutableMapping): typname = TiffTags.TYPES.get(typ, "unknown") print("save: %s (%d)" % (tagname, tag), end=' ') print("- type: %s (%d)" % (typname, typ), end=' ') - if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): + if tag in ( + COLORMAP, IPTC_NAA_CHUNK, + PHOTOSHOP_CHUNK, ICCPROFILE, XMP): size = len(data) print("- value: " % size) else: @@ -576,7 +587,7 @@ class ImageFileDirectory(collections.MutableMapping): fp.write(o16(tag) + o16(typ) + o32(count) + value) # -- overwrite here for multi-page -- - fp.write(b"\0\0\0\0") # end of directory + fp.write(b"\0\0\0\0") # end of directory # pass 3: write auxiliary data to file for tag, typ, count, value, data in directory: @@ -586,6 +597,7 @@ class ImageFileDirectory(collections.MutableMapping): return offset + ## # Image plugin for TIFF files. @@ -616,7 +628,7 @@ class TiffImageFile(ImageFile.ImageFile): print ("- __first:", self.__first) print ("- ifh: ", ifh) - # and load the first frame + # and load the first frame self._seek(0) def seek(self, frame): @@ -694,9 +706,11 @@ class TiffImageFile(ImageFile.ImageFile): if not len(self.tile) == 1: raise IOError("Not exactly one tile") - # (self._compression, (extents tuple), 0, (rawmode, self._compression, fp)) + # (self._compression, (extents tuple), + # 0, (rawmode, self._compression, fp)) ignored, extents, ignored_2, args = self.tile[0] - decoder = Image._getdecoder(self.mode, 'libtiff', args, self.decoderconfig) + decoder = Image._getdecoder( + self.mode, 'libtiff', args, self.decoderconfig) try: decoder.setimage(self.im, extents) except ValueError: @@ -706,35 +720,35 @@ class TiffImageFile(ImageFile.ImageFile): # We've got a stringio like thing passed in. Yay for all in memory. # The decoder needs the entire file in one shot, so there's not # a lot we can do here other than give it the entire file. - # unless we could do something like get the address of the underlying - # string for stringio. + # unless we could do something like get the address of the + # underlying string for stringio. # # Rearranging for supporting byteio items, since they have a fileno - # that returns an IOError if there's no underlying fp. Easier to deal - # with here by reordering. + # that returns an IOError if there's no underlying fp. Easier to + # dea. with here by reordering. if Image.DEBUG: print ("have getvalue. just sending in a string from getvalue") - n,err = decoder.decode(self.fp.getvalue()) + n, err = decoder.decode(self.fp.getvalue()) elif hasattr(self.fp, "fileno"): # we've got a actual file on disk, pass in the fp. if Image.DEBUG: print ("have fileno, calling fileno version of the decoder.") self.fp.seek(0) - n,err = decoder.decode(b"fpfp") # 4 bytes, otherwise the trace might error out + # 4 bytes, otherwise the trace might error out + n, err = decoder.decode(b"fpfp") else: # we have something else. if Image.DEBUG: print ("don't have fileno or getvalue. just reading") # UNDONE -- so much for that buffer size thing. - n,err = decoder.decode(self.fp.read()) - + n, err = decoder.decode(self.fp.read()) self.tile = [] self.readonly = 0 # libtiff closed the fp in a, we need to close self.fp, if possible if hasattr(self.fp, 'close'): self.fp.close() - self.fp = None # might be shared + self.fp = None # might be shared if err < 0: raise IOError(err) @@ -810,11 +824,11 @@ class TiffImageFile(ImageFile.ImageFile): xres = xres[0] / (xres[1] or 1) yres = yres[0] / (yres[1] or 1) resunit = getscalar(RESOLUTION_UNIT, 1) - if resunit == 2: # dots per inch + if resunit == 2: # dots per inch self.info["dpi"] = xres, yres - elif resunit == 3: # dots per centimeter. convert to dpi + elif resunit == 3: # dots per centimeter. convert to dpi self.info["dpi"] = xres * 2.54, yres * 2.54 - else: # No absolute unit of measurement + else: # No absolute unit of measurement self.info["resolution"] = xres, yres # build tile descriptors @@ -825,13 +839,14 @@ class TiffImageFile(ImageFile.ImageFile): offsets = self.tag[STRIPOFFSETS] h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] - if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", "group4", - "tiff_jpeg", "tiff_adobe_deflate", - "tiff_thunderscan", "tiff_deflate", - "tiff_sgilog", "tiff_sgilog24", - "tiff_raw_16"]: - ## if Image.DEBUG: - ## print "Activating g4 compression for whole file" + if READ_LIBTIFF or self._compression in [ + "tiff_ccitt", "group3", "group4", + "tiff_jpeg", "tiff_adobe_deflate", + "tiff_thunderscan", "tiff_deflate", + "tiff_sgilog", "tiff_sgilog24", + "tiff_raw_16"]: + # if Image.DEBUG: + # print "Activating g4 compression for whole file" # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) @@ -850,7 +865,8 @@ class TiffImageFile(ImageFile.ImageFile): # libtiff closes the file descriptor, so pass in a dup. try: - fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno()) + fp = hasattr(self.fp, "fileno") and \ + os.dup(self.fp.fileno()) except IOError: # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. @@ -881,7 +897,7 @@ class TiffImageFile(ImageFile.ImageFile): # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds - a = (rawmode, self._compression, fp ) + a = (rawmode, self._compression, fp) self.tile.append( (self._compression, (0, 0, w, ysize), @@ -893,8 +909,8 @@ class TiffImageFile(ImageFile.ImageFile): a = self._decoder(rawmode, l, i) self.tile.append( (self._compression, - (0, min(y, ysize), w, min(y+h, ysize)), - offsets[i], a)) + (0, min(y, ysize), w, min(y+h, ysize)), + offsets[i], a)) if Image.DEBUG: print ("tiles: ", self.tile) y = y + h @@ -914,8 +930,8 @@ class TiffImageFile(ImageFile.ImageFile): # is not a multiple of the tile size... self.tile.append( (self._compression, - (x, y, x+w, y+h), - o, a)) + (x, y, x+w, y+h), + o, a)) x = x + w if x >= self.size[0]: x, y = 0, y + h @@ -937,25 +953,27 @@ class TiffImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- # Write TIFF files -# little endian is default except for image modes with explict big endian byte-order +# little endian is default except for image modes with +# explict big endian byte-order SAVE_INFO = { - # mode => rawmode, byteorder, photometrics, sampleformat, bitspersample, extra + # mode => rawmode, byteorder, photometrics, + # sampleformat, bitspersample, extra "1": ("1", II, 1, 1, (1,), None), "L": ("L", II, 1, 1, (8,), None), - "LA": ("LA", II, 1, 1, (8,8), 2), + "LA": ("LA", II, 1, 1, (8, 8), 2), "P": ("P", II, 3, 1, (8,), None), - "PA": ("PA", II, 3, 1, (8,8), 2), + "PA": ("PA", II, 3, 1, (8, 8), 2), "I": ("I;32S", II, 1, 2, (32,), None), "I;16": ("I;16", II, 1, 1, (16,), None), "I;16S": ("I;16S", II, 1, 2, (16,), None), "F": ("F;32F", II, 1, 3, (32,), None), - "RGB": ("RGB", II, 2, 1, (8,8,8), None), - "RGBX": ("RGBX", II, 2, 1, (8,8,8,8), 0), - "RGBA": ("RGBA", II, 2, 1, (8,8,8,8), 2), - "CMYK": ("CMYK", II, 5, 1, (8,8,8,8), None), - "YCbCr": ("YCbCr", II, 6, 1, (8,8,8), None), - "LAB": ("LAB", II, 8, 1, (8,8,8), None), + "RGB": ("RGB", II, 2, 1, (8, 8, 8), None), + "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0), + "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2), + "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), + "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), + "LAB": ("LAB", II, 8, 1, (8, 8, 8), None), "I;32BS": ("I;32BS", MM, 1, 2, (32,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None), @@ -963,6 +981,7 @@ SAVE_INFO = { "F;32BF": ("F;32BF", MM, 1, 3, (32,), None), } + def _cvt_res(value): # convert value to TIFF rational number -- (numerator, denominator) if isinstance(value, collections.Sequence): @@ -973,6 +992,7 @@ def _cvt_res(value): value = float(value) return (int(value * 65536), 65536) + def _save(im, fp, filename): try: @@ -982,7 +1002,8 @@ def _save(im, fp, filename): ifd = ImageFileDirectory(prefix) - compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) + compression = im.encoderinfo.get( + 'compression', im.info.get('compression', 'raw')) libtiff = WRITE_LIBTIFF or compression != 'raw' @@ -999,17 +1020,16 @@ def _save(im, fp, filename): ifd[IMAGELENGTH] = im.size[1] # write any arbitrary tags passed in as an ImageFileDirectory - info = im.encoderinfo.get("tiffinfo",{}) + info = im.encoderinfo.get("tiffinfo", {}) if Image.DEBUG: - print ("Tiffinfo Keys: %s"% info.keys) + print("Tiffinfo Keys: %s" % info.keys) keys = list(info.keys()) for key in keys: ifd[key] = info.get(key) try: ifd.tagtype[key] = info.tagtype[key] except: - pass # might not be an IFD, Might not have populated type - + pass # might not be an IFD, Might not have populated type # additions written by Greg Couch, gregc@cgl.ucsf.edu # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com @@ -1030,7 +1050,7 @@ def _save(im, fp, filename): ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if "resolution" in im.encoderinfo: ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ - = _cvt_res(im.encoderinfo["resolution"]) + = _cvt_res(im.encoderinfo["resolution"]) if "x resolution" in im.encoderinfo: ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) if "y resolution" in im.encoderinfo: @@ -1077,8 +1097,9 @@ def _save(im, fp, filename): stride = len(bits) * ((im.size[0]*bits[0]+7)//8) ifd[ROWSPERSTRIP] = im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1] - ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer - ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default + ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer + # no compression by default: + ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) if libtiff: if Image.DEBUG: @@ -1089,23 +1110,27 @@ def _save(im, fp, filename): fp.seek(0) _fp = os.dup(fp.fileno()) - blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. - atts={} + # ICC Profile crashes. + blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] + atts = {} # bits per sample is a single short in the tiff directory, not a list. atts[BITSPERSAMPLE] = bits[0] # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. - for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()): + for k, v in itertools.chain( + ifd.items(), getattr(im, 'ifd', {}).items()): if k not in atts and k not in blocklist: if type(v[0]) == tuple and len(v) > 1: # A tuple of more than one rational tuples - # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL + # flatten to floats, + # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = [float(elt[0])/float(elt[1]) for elt in v] continue if type(v[0]) == tuple and len(v) == 1: # A tuple of one rational tuples - # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL + # flatten to floats, + # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0][0])/float(v[0][1]) continue if type(v) == tuple and len(v) > 2: @@ -1115,7 +1140,8 @@ def _save(im, fp, filename): continue if type(v) == tuple and len(v) == 2: # one rational tuple - # flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL + # flatten to float, + # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0])/float(v[1]) continue if type(v) == tuple and len(v) == 1: @@ -1141,9 +1167,10 @@ def _save(im, fp, filename): a = (rawmode, compression, _fp, filename, atts) # print (im.mode, compression, a, im.encoderconfig) e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) - e.setimage(im.im, (0,0)+im.size) + e.setimage(im.im, (0, 0)+im.size) while True: - l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock + # undone, change to self.decodermaxblock: + l, s, d = e.encode(16*1024) if not _fp: fp.write(d) if s: @@ -1155,13 +1182,12 @@ def _save(im, fp, filename): offset = ifd.save(fp) ImageFile._save(im, fp, [ - ("raw", (0,0)+im.size, offset, (rawmode, stride, 1)) + ("raw", (0, 0)+im.size, offset, (rawmode, stride, 1)) ]) - # -- helper for multi-page save -- if "_debug_multipage" in im.encoderinfo: - #just to access o32 and o16 (using correct byte order) + # just to access o32 and o16 (using correct byte order) im._debug_multipage = ifd # From bfb482dc66432d83b5da894c83886ec5c973c499 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 28 Jul 2014 21:26:39 -0700 Subject: [PATCH 441/488] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 16f8234c7..2da2c77c6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Added support for reading MPO files + [Feneric] + - Added support for encoding and decoding iTXt chunks #818 [dolda2000] From 78d26180647956b41372800eff223a3b5ca871d6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 28 Jul 2014 21:49:11 -0700 Subject: [PATCH 442/488] Image.point tests for Float LUT --- Tests/test_image_point.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 63fb6fc38..04054fa84 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -31,6 +31,16 @@ class TestImagePoint(PillowTestCase): im = lena("I") im.point(list(range(256))*256, 'L') + def test_f_lut(self): + """ Tests for floating point lut of 8bit gray image """ + im = lena('L') + lut = [0.5 * float(x) for x in range(256)] + + out = im.point(lut, 'F') + + int_lut = [x//2 for x in range(256)] + self.assert_image_equal(out.convert('L'), im.point(int_lut, 'L')) + if __name__ == '__main__': unittest.main() From 1a245a577bcc5a0fb2b73e0d2ca2b6827b31274e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 28 Jul 2014 22:09:52 -0700 Subject: [PATCH 443/488] Mode F and I tests for Image.putdata --- Tests/test_image_putdata.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 036ee285f..acea0d62a 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -46,6 +46,26 @@ class TestImagePutData(PillowTestCase): im = Image.new('L', (256,256)) im.putdata(list(range(256))*256) + def test_mode_i(self): + src = lena('L') + data = list(src.getdata()) + im = Image.new('I', src.size, 0) + im.putdata(data, 2, 256) + + target = [2* elt + 256 for elt in data] + self.assertEqual(list(im.getdata()), target) + + def test_mode_F(self): + src = lena('L') + data = list(src.getdata()) + im = Image.new('F', src.size, 0) + im.putdata(data, 2.0, 256.0) + + target = [2.0* float(elt) + 256.0 for elt in data] + self.assertEqual(list(im.getdata()), target) + + + if __name__ == '__main__': unittest.main() From e2a8a7a6a1c958eb8b400503029e05b447f83f22 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 29 Jul 2014 13:27:13 +0300 Subject: [PATCH 444/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 2da2c77c6..87bc452c0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- PyPy performance improvements #821 + [wiredfool] + - Added support for reading MPO files [Feneric] From 56404f6888d9ab19563b6a2103c1018a6db93519 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 29 Jul 2014 23:00:38 +0300 Subject: [PATCH 445/488] Change wrapping to include some context on first line; plus typo fix --- PIL/TiffImagePlugin.py | 43 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index faa7ff6cf..9bef30ebe 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -453,19 +453,17 @@ class ImageFileDirectory(collections.MutableMapping): data = ifd[8:8+size] if len(data) != size: - warnings.warn( - "Possibly corrupt EXIF data. " - "Expecting to read %d bytes but only got %d. " - "Skipping tag %s" % (size, len(data), tag)) + warnings.warn("Possibly corrupt EXIF data. " + "Expecting to read %d bytes but only got %d. " + "Skipping tag %s" % (size, len(data), tag)) continue self.tagdata[tag] = data self.tagtype[tag] = typ if Image.DEBUG: - if tag in ( - COLORMAP, IPTC_NAA_CHUNK, - PHOTOSHOP_CHUNK, ICCPROFILE, XMP): + if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, + ICCPROFILE, XMP): print("- value: " % size) else: print("- value:", self[tag]) @@ -550,9 +548,8 @@ class ImageFileDirectory(collections.MutableMapping): typname = TiffTags.TYPES.get(typ, "unknown") print("save: %s (%d)" % (tagname, tag), end=' ') print("- type: %s (%d)" % (typname, typ), end=' ') - if tag in ( - COLORMAP, IPTC_NAA_CHUNK, - PHOTOSHOP_CHUNK, ICCPROFILE, XMP): + if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, + ICCPROFILE, XMP): size = len(data) print("- value: " % size) else: @@ -709,8 +706,8 @@ class TiffImageFile(ImageFile.ImageFile): # (self._compression, (extents tuple), # 0, (rawmode, self._compression, fp)) ignored, extents, ignored_2, args = self.tile[0] - decoder = Image._getdecoder( - self.mode, 'libtiff', args, self.decoderconfig) + decoder = Image._getdecoder(self.mode, 'libtiff', args, + self.decoderconfig) try: decoder.setimage(self.im, extents) except ValueError: @@ -839,12 +836,14 @@ class TiffImageFile(ImageFile.ImageFile): offsets = self.tag[STRIPOFFSETS] h = getscalar(ROWSPERSTRIP, ysize) w = self.size[0] - if READ_LIBTIFF or self._compression in [ - "tiff_ccitt", "group3", "group4", - "tiff_jpeg", "tiff_adobe_deflate", - "tiff_thunderscan", "tiff_deflate", - "tiff_sgilog", "tiff_sgilog24", - "tiff_raw_16"]: + if READ_LIBTIFF or self._compression in ["tiff_ccitt", "group3", + "group4", "tiff_jpeg", + "tiff_adobe_deflate", + "tiff_thunderscan", + "tiff_deflate", + "tiff_sgilog", + "tiff_sgilog24", + "tiff_raw_16"]: # if Image.DEBUG: # print "Activating g4 compression for whole file" @@ -1002,8 +1001,8 @@ def _save(im, fp, filename): ifd = ImageFileDirectory(prefix) - compression = im.encoderinfo.get( - 'compression', im.info.get('compression', 'raw')) + compression = im.encoderinfo.get('compression', im.info.get('compression', + 'raw')) libtiff = WRITE_LIBTIFF or compression != 'raw' @@ -1118,8 +1117,8 @@ def _save(im, fp, filename): # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. - for k, v in itertools.chain( - ifd.items(), getattr(im, 'ifd', {}).items()): + for k, v in itertools.chain(ifd.items(), + getattr(im, 'ifd', {}).items()): if k not in atts and k not in blocklist: if type(v[0]) == tuple and len(v) > 1: # A tuple of more than one rational tuples From 13eb3d667afb8df8b4ac9a68a4fb936b1f8f3e7e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 29 Jul 2014 20:44:17 -0700 Subject: [PATCH 446/488] Added profile.tobytes() for ImageCms Profiles --- PIL/ImageCms.py | 2 ++ Tests/test_imagecms.py | 17 +++++++++++++++ _imagingcms.c | 47 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 4ea6409d6..9848e5ba2 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -169,6 +169,8 @@ class ImageCmsProfile: self.product_name = None self.product_info = None + def tobytes(self): + return core.profile_tobytes(self.profile) class ImageCmsTransform(Image.ImagePointHandler): diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 152241f90..74eef3037 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -210,6 +210,23 @@ class TestImageCms(PillowTestCase): self.assert_image_similar(lena(), out, 2) + def test_profile_tobytes(self): + from io import BytesIO + i = Image.open("Tests/images/rgb.jpg") + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + + p2 = ImageCms.getOpenProfile(BytesIO(p.tobytes())) + + # not the same bytes as the original icc_profile, + # but it does roundtrip + self.assertEqual(p.tobytes(),p2.tobytes()) + self.assertEqual(ImageCms.getProfileName(p), + ImageCms.getProfileName(p2)) + self.assertEqual(ImageCms.getProfileDescription(p), + ImageCms.getProfileDescription(p2)) + + + if __name__ == '__main__': unittest.main() diff --git a/_imagingcms.c b/_imagingcms.c index 1b7ef49e1..3b822006a 100644 --- a/_imagingcms.c +++ b/_imagingcms.c @@ -49,7 +49,8 @@ http://www.cazabon.com\n\ /* known to-do list with current version: - Verify that PILmode->littleCMStype conversion in findLCMStype is correct for all PIL modes (it probably isn't for the more obscure ones) + Verify that PILmode->littleCMStype conversion in findLCMStype is correct for all + PIL modes (it probably isn't for the more obscure ones) Add support for creating custom RGB profiles on the fly Add support for checking presence of a specific tag in a profile @@ -134,6 +135,49 @@ cms_profile_fromstring(PyObject* self, PyObject* args) return cms_profile_new(hProfile); } +static PyObject* +cms_profile_tobytes(PyObject* self, PyObject* args) +{ + char *pProfile =NULL; + cmsUInt32Number nProfile; + PyObject* CmsProfile; + + cmsHPROFILE *profile; + + PyObject* ret; + if (!PyArg_ParseTuple(args, "O", &CmsProfile)){ + return NULL; + } + + profile = ((CmsProfileObject*)CmsProfile)->profile; + + if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) { + PyErr_SetString(PyExc_IOError, "Could not determine profile size"); + return NULL; + } + + pProfile = (char*)malloc(nProfile); + if (!pProfile) { + PyErr_SetString(PyExc_IOError, "Out of Memory"); + return NULL; + } + + if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) { + PyErr_SetString(PyExc_IOError, "Could not get profile"); + free(pProfile); + return NULL; + } + +#if PY_VERSION_HEX >= 0x03000000 + ret = PyBytes_FromStringAndSize(pProfile, (Py_ssize_t)nProfile); +#else + ret = PyString_FromStringAndSize(pProfile, (Py_ssize_t)nProfile); +#endif + + free(pProfile); + return ret; +} + static void cms_profile_dealloc(CmsProfileObject* self) { @@ -485,6 +529,7 @@ static PyMethodDef pyCMSdll_methods[] = { {"profile_open", cms_profile_open, 1}, {"profile_frombytes", cms_profile_fromstring, 1}, {"profile_fromstring", cms_profile_fromstring, 1}, + {"profile_tobytes", cms_profile_tobytes, 1}, /* profile and transform functions */ {"buildTransform", buildTransform, 1}, From 5966278643e356957986c52679184add492bf553 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 29 Jul 2014 21:20:11 -0700 Subject: [PATCH 447/488] Added im.info['icc_profile'] to results for ImageCms.applyTransform --- PIL/ImageCms.py | 4 ++++ Tests/test_imagecms.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 9848e5ba2..e708d1dad 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -199,6 +199,8 @@ class ImageCmsTransform(Image.ImagePointHandler): self.input_mode = self.inputMode = input_mode self.output_mode = self.outputMode = output_mode + self.output_profile = output + def point(self, im): return self.apply(im) @@ -207,6 +209,7 @@ class ImageCmsTransform(Image.ImagePointHandler): if imOut is None: imOut = Image.new(self.output_mode, im.size, None) self.transform.apply(im.im.id, imOut.im.id) + imOut.info['icc_profile'] = self.output_profile.tobytes() return imOut def apply_in_place(self, im): @@ -214,6 +217,7 @@ class ImageCmsTransform(Image.ImagePointHandler): if im.mode != self.output_mode: raise ValueError("mode mismatch") # wrong output mode self.transform.apply(im.im.id, im.im.id) + im.info['icc_profile'] = self.output_profile.tobytes() return im diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 74eef3037..e731c8945 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -2,8 +2,11 @@ from helper import unittest, PillowTestCase, lena from PIL import Image +from io import BytesIO + try: from PIL import ImageCms + from PIL.ImageCms import ImageCmsProfile ImageCms.core.profile_open except ImportError as v: # Skipped via setUp() @@ -118,7 +121,7 @@ class TestImageCms(PillowTestCase): def test_extensions(self): # extensions - from io import BytesIO + i = Image.open("Tests/images/rgb.jpg") p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) self.assertEqual( @@ -196,6 +199,11 @@ class TestImageCms(PillowTestCase): # img_srgb.save('temp.srgb.tif') # visually verified vs ps. self.assert_image_similar(lena(), img_srgb, 30) + self.assertTrue(img_srgb.info['icc_profile']) + + profile = ImageCmsProfile(BytesIO(img_srgb.info['icc_profile'])) + self.assertTrue('sRGB' in ImageCms.getProfileDescription(profile)) + def test_lab_roundtrip(self): # check to see if we're at least internally consistent. @@ -205,6 +213,10 @@ class TestImageCms(PillowTestCase): t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") i = ImageCms.applyTransform(lena(), t) + + self.assertEqual(i.info['icc_profile'], + ImageCmsProfile(pLab).tobytes()) + out = ImageCms.applyTransform(i, t2) self.assert_image_similar(lena(), out, 2) From c7a6f162caeea7bdcff2a2ece641eedf58cf9aa3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Jul 2014 11:30:46 +0300 Subject: [PATCH 448/488] Add new pypy3: http://blog.travis-ci.com/2014-07-24-upcoming-build-environment-updates/ --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b37588843..0dfb1c199 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ env: MAX_CONCURRENCY=4 python: - "pypy" + - "pypy3" - 2.6 - 2.7 - "2.7_with_system_site_packages" # For PyQt4 From a5302dcea908fa4e9ad8662cb7d08852b6368981 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Jul 2014 12:04:04 +0300 Subject: [PATCH 449/488] Cover pypy --- .travis.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0dfb1c199..934d8ebf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,18 +33,12 @@ script: - python setup.py clean - python setup.py build_ext --inplace - # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640) - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time python selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then time nosetests -vx Tests/test_*.py; fi - - # Cover the others - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* selftest.py; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then time coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi + - time coverage run --append --include=PIL/* selftest.py + - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py after_success: - coverage report - # No need to send empty coverage to Coveralls for PyPy - - if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coveralls; fi + - coveralls - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py From b46f5c6b1e87e19866baad6b40098134d602b392 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Wed, 30 Jul 2014 10:14:09 -0400 Subject: [PATCH 450/488] Better documented limited MPO save feature. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At present it’s only possible to save the current frame of an MPO, not the MPO in its entirety. Added testing verifying as much. --- PIL/MpoImagePlugin.py | 3 ++- Tests/test_file_mpo.py | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 520e683d4..0d1185205 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -25,7 +25,8 @@ from PIL import Image, JpegImagePlugin def _accept(prefix): return JpegImagePlugin._accept(prefix) -def _save(im, fp, filename): +def _save(im, fp, filename, **options): + # Note that we can only save the current frame at present return JpegImagePlugin._save(im, fp, filename) ## diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 20589539b..a221eec15 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -13,7 +13,8 @@ class TestFileMpo(PillowTestCase): if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: self.skipTest("jpeg support not available") - def roundtrip(self, im, **options): + def frame_roundtrip(self, im, **options): + # Note that for now, there is no MPO saving functionality out = BytesIO() im.save(out, "MPO", **options) bytes = out.tell() @@ -106,7 +107,19 @@ class TestFileMpo(PillowTestCase): im02 = im.tobytes() self.assertEqual(im0, im02) self.assertNotEqual(im0, im1) - + + def test_save(self): + # Note that only individual frames can be saved at present + for test_file in test_files: + im = Image.open(test_file) + self.assertEqual(im.tell(), 0) + jpg0 = self.frame_roundtrip(im) + self.assert_image_similar(im, jpg0, 30) + im.seek(1) + self.assertEqual(im.tell(), 1) + jpg1 = self.frame_roundtrip(im) + self.assert_image_similar(im, jpg1, 30) + if __name__ == '__main__': unittest.main() From 3f0ff0177e973c67efd756c73128b9d0a701a171 Mon Sep 17 00:00:00 2001 From: "Eric W. Brown" Date: Wed, 30 Jul 2014 10:16:51 -0400 Subject: [PATCH 451/488] Dropped unused "options" from MPO save. --- PIL/MpoImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/MpoImagePlugin.py b/PIL/MpoImagePlugin.py index 0d1185205..d053d9026 100644 --- a/PIL/MpoImagePlugin.py +++ b/PIL/MpoImagePlugin.py @@ -25,7 +25,7 @@ from PIL import Image, JpegImagePlugin def _accept(prefix): return JpegImagePlugin._accept(prefix) -def _save(im, fp, filename, **options): +def _save(im, fp, filename): # Note that we can only save the current frame at present return JpegImagePlugin._save(im, fp, filename) From e49e689c70eb243322e57e9213df9f804c85d762 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Jul 2014 19:33:04 +0300 Subject: [PATCH 452/488] Add JPEG with IPTC data. My image, permission given to distribute under MIT licence --- Tests/images/iptc.jpg | Bin 0 -> 21019 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/iptc.jpg diff --git a/Tests/images/iptc.jpg b/Tests/images/iptc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b4d49caa182b1a5db367629b2d2719db86566188 GIT binary patch literal 21019 zcmeFYc|26_|37?Y27~MdV_##2$TDO%2xDh#k;+zPC|OcbG7@F%*(WiWv1=t%N{b~? z_Owa%U5b({_ZfP>KcDY?-+$b{`@i4g@jH*lne%d;>s;rxJYUOooy+#n_5wh1Ae=sP zI*f2SSV}`h70@=Zv}Ixf7>BPI)B|8O3HS33J|1>P40;@Z?nw=+}002u%MSvB2 zEi1qcfdVWb6$G9|+_{-;0jD$`DX zGT8nN9Kh~$AkiDJJD{qp2J8d>W{mmIRS=+D-W@#`Y`*!Irh>G>wcQb;z}`R_9AGF) zRb5>bmIeh7AYBgjVfsry25Ct8ZXXDPlk4m|Z|Mfxq1SiE9|CFC>$_tzhGhFMn*DE| z5wH(H{rfI8RaFS}KT<)dyHa66b;179cR;2fwEv6={Vy8!Uo;bKmxpO^e3t)ehySG+ zuVozLVgP_O;Rd8YnjJhD_SRKBpsT7arLLx{hS9}n{=c@Bi2}eh3;-O!f&9Sdl>a`@ zpcy9%0Q+kzhOPgxry*##f9z=v(*JEw2y|CkJFp%4m#*gp9|jK`crrNt=ej+#{SmkV zFvFObm|)D{4>L0}3!H-$4j$Zl_ONmAa`W-=a`W=?3yKNxBSZvvd6Cjc5pi)zNlAVo z895mVIWY-I2?+QYoCVIs%E~2y;6+IMFCW{@052UHdzh)Hvyl}9(F-F%-6lk43Tyu`u`@- z<^Nw2{VUMF@@#(qIA9RaU@%_52pGMRI8AIHNpig+hr8Vl@Y{`v<>Q&iIXqv4AHTkk z=tB(=>(Me=q6Zl^XdZJNWWJRe;Ty%VXJ3!EeqxsIub4OQJ`DY~#}cmNT@O<%EW!pa zAu1%;>)&qtym-mVh#u}s`Lh9gz?ZY;`&+iXX2U)5X{S~nNnjvM+Dsc_?Il*ygewua zG2|*y5`#xokE=Z+c06`wf5injUH+yRq$H`y*`z~1Qc}LD^p*Q$WY#g8P4uf(Do-x* z%?NNysO9&mOX`uieUi3a*p1ZUCrFO->K}*Q?Y9h4XO1XGZ5@639<>czHd?pRuiOS& za;i33;D|>Jx2g`cdz{yyDPM)n**mqE8i$%`Di*}wyS^t?VrAL=F1dC#&Cu8Ftt?&T zn_4ULMfAcUmkfB}b2`V_WirdP(i~m^aeIM|waPcsvyQ`Nr}RF`u}U}!qsqd950?f< zIz^gqd5a(So!%dR@w`^wux0d$_pOZZsI6mLGZq`X+d!}5xLa@a=CMg&E3x1?2;?TK!_iY^2DfcCp)`p%rPlj059BV+(B7IJJ)*av|$iNxO& z0XHJ-oK1KIlkZ!>``${nC!L(?l^?%Af7DHBFqc$<4DlBJtaw=3M64kCTgP_QHLB?u z)P1-{_ZKyZ!(Tc%amUT*Y|YkpE%LybrJ1R57p)Sd`=2j~Mz`l3-&%RPu2xli`|0a# z;4`Sfc!maTESAe=w*gi9t6TS_pJ#?xUJEX(Xs_UFlqt|4r*od@I9M5NbzZ01-8XaD z{%9Qs?c(bs5rHj9cFR2~5iea%pc&Yi;Y#ma5aAXh zM;2(f-+PSp?_uh$`ClAc?2A$?9B z=&&O<iH969rK-!l4VT> zjML1B8dIAm%?^^*7mpfG-kzy9RPTNe?Y-9Kq^4kPYvLQm0{=#eaBLO}BTv8Z4g9({ z^7KUCqFRNyy5~I$>(UMPT+gy{L{RTM2|Pgl`r@9t|n`3 za&P=O(zbu=vVD0Cv*w~%t2ISsdX!j$T-EoXIki~tOSg|qw>|M%qU?JG-@WU~lKc5S zYfpX1FM8#B}qC27Ji$TB=(mu!Fj!hrczk9>a1}$`g(8p z`~SS*>~;Nud84WksXv)@Fy+`4i^@qqJ{K)kbWLyZbL3wvG4xF1$82&1)MhP58UTX=BT`a^2`If3Dk4 zT9;|xj*vXC_;Z@*Y7;iZ@$Ol{vzE1jv4zNtI3HK(JCXhh8(RYOuSoqww7hqQtQY@V znB-<;Xs?#8Tq9AI|L~J*v-Ql0d^LA?Xi3~INQ7cSVEe6{TleIaFA6g$6Z{AtlkD5w0_0sYCiToO|y;_OnsYlj&> zcY*fX0W<5kdi9WyPbaell{>Sws<}7SoZe0xNqr&7^M1`Eq9!>h(D%$Ux~fUhz#8Nc zR8fwbfANtd0AnJ=)0ouz$p-@J+tV zL(DzHo@`+`%EFD~De0{MnPYiYXd9@pE1j}EQES2BlG#^el?T)^2DPh z&Nug_e+HEWf9HYfjX(}=Ar5{BF{?2jR+_VTkY{Z^2EV((?dy4oKJq)~4CVMn(bK7{ zR~=hS9R22Ydcxp|e3u>BRa|+@U9YtoD3ChhRw?bTbNB7dld7kLb|R`Bv%Pc{)_j-g3QIx4?yH zLU)cFw@Gc1>1i=6UuBW`5?0OgOOmOnKJ-Ixzbt2$^buOUz8|;Mn>|PDBBS}#Yd=Om zI7T~ggXyHKNEt}OJC#GHat!e72+v~$Vc ztJj&kZZCNgwT$_r@l(lhCjRttSx54vn(&6z<$XydT4_OGMKU7trEl`^P2J%U75(-X zywkYxu#cL2$q&>j?9;w#I)Y`9_1mw|bL|^}(a-o)1Fx|GUO4!;#zy7DHxX+O+{`C5^wHVJXiGd`-%ONhYK!=JHAqR3F{MRES`Jt({&VCGm!tibLo|S z*kRi+aXf9Z?!>+GW&2?7sdf{7j~I1F^PRFJLD=Svtr`)y}fSFc#NNPMkWW03UGI$>doe4ef( zcakgQ-OwMArCMcIWs`nGllI5B*t@F|!g=62ET1zgzQ=8>v zl@+zH(!|mR%c)P;9)8>gq}^uTN^S-OtSQbzjp~j!&s{cCA<8t-avXckD@H$(uh90K znvl~xD-zyNcPGA|?>Xu@G%}yV?++zM{uh77g z<&`hL|1@nJiC&|?V==?<4@)uP1O3tSwS8%uw5zvc=PeF@Hr?6+vn2mvUGqY&8v&=j zQX=?6PImmcrXa2x(q>irUj4>}tngH>q1kgv`uC5Re8n1C$8i|6as?Ex=~)ZQM`2^P z0o(O0lPbLPhasWoBDk5Kk5jfnHWatAirr`K>pysFI3;>WKQ3Nl8yGC|6wC8@v#xY` zHob#xrvob-tSM4XiE3g-Tcp>nGaVUdJN1?+rK!9bE>WkY%%VMuhLSYW4A30VTih@s zD+kE5t}}2|G_v=WHgc3CL{f`cz=eFIv#|#0%>Bl59}Vb{2?pSF$W<@x(dzW~@M&EK zQH1nK`T1q?Y5mb^c9D}T+HReqhc@0AeOOvMTZ8P_%DRz}5gKJoNS8^EdHdCNuD|=W z_*-MLp1+tgCjb{-vYisQpXg1Km*);;ZKK0%ZDCmeTgU{Cgz#8wx*9zVDo= zWR94NG%F1ci%lx@C}xJIo_>?tX)Kil8LnU-vF}4P!cwQqUb2V8q!$dxk&nCX?gim}2|SQ0sNZVsTW-7@9(g(WY}X=1O}jw=lI{Ty2HsaAOm$Gn#SkWQg7K zJD>7-Kh{YZq231g4S5!nfk-3H%o=9lNa9zm#8eJ&jBs79IBAO1T2Lrgpv#-9eW?MW zx8Lt9+VJe*;NoEC;o{`tcm~{URd!eND7YeE*k^okbhS2$b}H7H$6bU9@>y zT%Ja!K~H4I#36uM2WStL%?v;|07N#xAH$Kv*Og&!K;T)jN;^Y6o@@BES|@tYa%*`j zybxc|PT9Zq>&8}N;lvS%!Z(FM(KD27Kyl%D)a{&Ef2Sg!>_;{xWB^5l{{2A{0Y@w| zg7KpaAn*Vkyu+l7Afc$<){ORAJWw11Uh&-!1`CXzTEG#o`ZgE52G=c-95pz3lQjD(=BV z)^RVbv`blvE9GA9v5S-!xtG2l`<42mdYxjBzAt)t$!Ig|G$_iAZ9wYd>Gkoa9gl(w z11Hw=oTuH3|BB)b@`UH9-QkV#qr4*xg5-e$S>IzO9k;`pHEu^_=3){%*iK^iU;?Er z1{KUY9%df+Ru_gjk;be-0B;FlPLTL9)NUx*1JE!h0A0dVA0F0mXLXNf3SScJzgl;* zFO(kS^7T*J2JR7fzL;%QP6e-oxV%}PWxD%iR&X1*^j=PKKHo(n{E4e;!q^F&XNX3h zRhx4v3L6*fw*k+Ozuhdo)VzZXWRFc8(YCMqzKN^qzH4?iHx3aGYB#f%)ym_)OvI2Q zjbRE02*V^-5VagAD)T>046A?uxlA@?NAcjh)zAc3YVqT-;2qICdnXwZqK81Mvd9y| zuq*_I5~JRi_FC2NM=z%6L|7UFyJ$4D5^09h4d$PHBDj(&^iOdjc6wmm`!wB z^)|5j>S@-Q?C9kw_+}VGfkvPLEvvdIa1t0^KJ3RakJm7Zi&OkSI2OoRPq;?nq zwc&x@b%DPShf9;yj)pWa_`0$yjRSNtsT=Bu^XZ{@Au>VxkOB*HU`_tR(bbYxQ9w1Z zXB((aT`t;FQ5rqgJGSF|nq`7%+d#w9j)%b))Lf>m&CVj@c5%tDBg1GrKcMB98P3LF z57Y~7$HEM!V{p2oX3+Ai;C&j^htU<%?szn$8{r@Ys;O~DRS=3zH~7@^Y!I^{*ga%5 zEKmLLX!$TW8I!a^rXS5OoRi%kn9dRl*M1akHBx43T9wBn=Tj$+APw>~Bl#SUv`X)? zBLx~1eC>6T;NRG=3p|GRNE4{uX3~3hjE5$I{Okk=kZ*?ib`XGqrO~<~TEBvvkeNV4 zPF@G`0Uw?*vsxtXS0E z9c8r?$FO~1u*ppAR;5#6m^_BxK!e%JANHAu!3E4WE-hcmR4cwyR<{0fBfy~xj4r9@ zZQ#?b;{B_ZThq6yB|6EU;rMs|Ls^W#?T9n}Clv0)KQk%NtPHiBd5v&^4*p>|tm}aV6sCEP}s3Yb?ycRj2Y`Rp#!Rz2u zV)VpuTG0{LdwT7A;bzCI?>6B2)cD9DgFJzHlb$1`(q>S>>oJM{x+IhlH9!`29f0AA zJFdyVKEoG4R`JLk4es3fYxJ1>6GXF!)=`EB09Se_fQLp4ARih*XCWcPdb=UUR3@Dk zc7qEJFpU-g6EWRBx!ME!xE82*FV51|#GB)VQ_jLB!)dzA)qRf$y`Qa@wgCyJZQ#*n zV1<3dQ>*!B^PN>easT8KhTisqSpa zma9rFN!eONChNx;*_EWiepmgrEqO`OmPt|2Ca12f@zb$4(Z8Z=!qLCry^mBcXo!3) zBHZfxPap=p4b;~zI{yZe-S~xP+yOcBz#<&f7$hT`fWEb(?42$l4GfkOTNur74>d*@ zd20ojrmTeEV6Yk$Z7K|L#erG$k@SA0XO-5<0~x;A_NMKXN!8Ehiq3MH3*QkPKW*yf z5+f!yP&dFD$&v2rdf!)L(00B0$*a@}=OFEVcz3j7%2QdpYu!f6GY9@W9ekMer)1_x zu0dY%sqR`gBI&<;{eMJo2270^fd|ajATk&rVWf5>BM2}c4a&^C{bdW zOuluSw>k5qVN(?zeckL+5^GgvM4ib;Q>|x-Upm^>Vx8SgH}xgGH>$Yne2>PBfhfw@ z1{#l8ZT76(`XtfW`e52askRy9 zGO{XvRVqPGJmoBRqx2d}>j6}icS%E-oSi61ZMb#1=J@4|DY>?)zXgt?uPH@J5>Ovc5$!7(466CBT^OQyGwj1C&rPmB z?h6JBMJo*(V@CM$f~NEXSIL25>{y4!sJHcX7QGchyqxB}5c$@*4~Z!UD`fY7_H~tc zq<3enMX*s>u-7FhzA(T4x|mM}67>E!B1F`@S_fL$K*P$YRzR9B!8f0u+{NJHe{#H}V`FrZxf&QI}67~15ch+~&niCV+yqO!r6Pn`@G zVc8MxpCas^(&sP6;&;ubyz331r|S^Z5;6<3EX>SM+R+f_UvLQcERQBsL<)3Z~*jWqv>Kr9j z95S&uckbjRcQK#Zg^P~if=rci603degL_*veBQnMd40r6M0PeppoY0$XiqNP^=;9u zhAv!(V~Bilb=rSo07D62MBasIE$CBVlHE0*&kk~Ts=$9Os+IJP5~QpWRSrRe)eAEW zu~V*Mmfe5ZH~oz5qXY(FvM1795*+a+%*_o_rwE*`Bmhh-%!V4tUv0W!|9O;NG>VPyU1(Zr$a zv}`^FyMVnKA0N2n+23s;3V-x!y!7$qfx&T@{OU@;`?_UM`T%5ZTMi{Y$QE~4^4Mx4lfb~uR$3QkiAG=MAE>hYb zl)4TGCUwJz1Y}crDePJ)1<=Fu;|}5vz~%2B;#R$*QyPC2%1SSNUu>3$`+Y*wUw}Il z`MFW^=;)BPTsYMXDl8g!rp)VSrLvl-Z;iERmRIvgAIwBJ+c71120u!l#^(1$bV-S+ zfrt_(K-hcgKg6)b*bvg&lyL05hs-xwI9)RAynh8`+#$CL7wZU1jF!c^G(9ldSF<*F zx4auuFj-Sip7dnvR$Z+LJk96cMQZc@b*tRfK1{*>ZkfR6$6sbAoYwjW9uo875Y4+q ztOs^4LsqaH1Vhix1e#G??-Y+a72;p7meL+Z-NsCVv1(}-8d!EaVsaY>Vl`{CQCvli zV!u|~i_@mwH)sBcT-Ds~i1(9Mv$8<2-Zr?`&Ruh&{Wj#8FhZH-=Cv1?t5;^`@NpTB zd}rK3>m?dpnbg{AP%5rw>@Qii&KMl*lcy&2v)VXfuTam>z$(Ew1+9i<`JmOUmPC$> zad<`KU-e*e&9HImpj62hDxR6Fz#a5m;E~MAyA)sZaV}?YQ%>%}U{7UtzLTc%<3r6c z`d)v?1NF0@S2?&MVIL)Z| zk^j_L+8;cA*j}MBCb$5Y0wE!0fV3w60|=GlS5&7RTj^=on*D?`o*U!4SU+KR_2l)W z6KNkY@94TT(hN9>*A#6G_WTF`WCjOlo}>`U?-^TDimTl(F9iwOS=^?3wlB0- zoH)fSO_Sy{)^An&zFNd_?dXAEMZZU``bkcA?c~13a>S-v>CkYEC9;Ss@+cXSD4CVY zf(dw$rub;+qw4AjWyZT7hoboeb$=u*ggpPDbZg}BwM{%P0VhpQQLEcOJ|v*CQ6 zuYAKS;N#rjeaIWbtD1cx6^ni&gDpc#H8gE^H^Zysd>(V-x4jQ+)wMo(ph*lR#KeJ! z+09>!dIwYk=tv&_rZnvjdhc@2FiY>GHxw{6c$ol63b@&s?E$S^hLDhK8;%c z0=Hfg;oP;0^oV?J51v1kc(^t#YShT1Hs`_jsP{R` z+BKPd8pPt@XIDFn*YN|pp+y;7O4$vqjO0%TGvn_32bR=c{P+dc2Ao73-6`e4ywOf& z&0k%qo+%6~v?34!`mTB$PieJNb1Q;4vu9g>%Fa%gwk88=I50qHcB{4k{+=H5dvoYm zuHjmF?Ypz@5%XhB8pg}*?V)9qdV%(^_>t~`_UrNr;)*xFXk)IJwg?ND$7Uw|@Zc4r zO7;5TBIe92A!J<#z-%u}2vf_7Hmn7FNN7?6O?!pPf^f=@aWtUW4=_s$e6hW@GFtT{ zJ36lNR(HEc$5&ndZ)cl9JjmzT_WVF=#9mhAcqJDL1F|g zq4%vcx?NUiokJTvIFndAut2?84$-ji0DM)>SGoz7>1C%Oer5$+`Ucl`5zPH^KujhB zCzsanJ6Q4Z#M-UkBG1AaAX|yQXP+UP5%|#{sPK=pVJpL%*QMyxyz0zKH-iNA~byqnw zE6BFk8CnYfhFxTrDmAD$5hD8W;AaPC?y`fC9FFBYPqK}!^edOP>xx`#J~$Y*KOpGi zmFYuKiUU|V>(}j1=2O=Oa#ugDZ37dffi5lywAYU3i`*k}BB?&@p!jH-b8?AherLBIQ`!{-Vk|^O7fzEmAn= zvHl8}L$<&pb8$||fMcc>sCi37BC4a{X0m>bwX0v2{{!yp+M4D#chO17WI48ErdT|O zPj!zJoZ_kg7yEM}#*#HpmU0d=)a`P^Tq@mi-X<}RUM{ESh=q*1MtvBy8Kr#U94r3IYIZcO~{_rT8dr5pXSh($x5iZ zOg7=_X9qZuR3@1;c0eoD%2cGFpX42*S6-?~^RFZ`nGfx< zb8qi4-lMmjHfE16n+?qs{+X?C5kf1j-5Uva`aQ64poP!#h{YbWrx9&M0@aFWJ-)G` z0d^%5F0CC;hy?R>7?yc|FNvRAr8mMyfM#nHgxIvRl=rZFwYSeBQHALu-_TQZ`}QjOf->p3lFtq+bn;}6Q= zSt~tuzPK0XCNDcD2!6#)X)0Pa z`1vEab2GHd_T^6FN`+KDT2HEQJGye3N`d7iVQzQjB8&on3m6Q?_eJ=SIEyo0!|MQv zLk4CCUO9b81mXv{T@YXHS$t-Oc$ap_Za{DRL_KXl_3wkW@^5IK!}X2X+6l9;eA?2i z6PHiO?wo58_~aK-8-81L8cG18!wHhmcWMZh)7Lo&w@)ypxbfhtF^XwSGqn5>0Mlu% zSa=sU)z>61f9xo;aD`m*8%U-2AqpYnlZQ>;K2beeXTpsPibVXWSxXLTa!tQ;bF6Wo zL(6{CbgbaQE#u#QMy9Uc)GX7w2PNw4Ci@R`mVB##W?SWpgy_4795c}#v$>9yr+;{o z`O1=YsLd*>ar1kxsE3G8rgL5*SZuvwaV7&D(OGZM=LU*Ln-6qZy^x7%x+=vSfhcAD zSV_)MM=Q(1q{1A~N*g*Y7}gu-rTp+|dM-kN*G;99pS@~}>~-a8oDXnUKIFixWx?7= zv&;=T79pDIkrzaR-3nMycE3*lb;9GOif_=G;YzjbRbhwx)57b0F)K1OV~QsTGc^jJ zMrKwOYEqUG%+3Wmz9hw^?CvH*WdD~d8s)VJc zs8vL%OBY5LF9+5gqFMlj$a8}v*|Q@*+`ns0VtTAYYICXJl+E*jr$zd3#@=kmB>5oE zC#|6_NBD4s0PmM%`T0In-~*g+nc`%IblPh{(a2T0#i#Qv(k<7g$hzGGX=wG~Pq}Xf zUmcq;AAcqEj6WZG@AEWu}QDnQ|41NT+Y_JoOCHpi}@UR3;Ymdm-npQa7C~%l#nmjtf?t`pn71~ckVgxPx6HVkg$4s$k6Q^nSSQB1RHcnYR zPt@=|V89)jskh%8qb5D0pU{)A-k*4WYzZLTIh1wUdDRI|@3h@GwBCDHOU{icILxjc za|H>;-r}4(0KkLaN{II0yks($%L4T*GtDC8u$D(L4r=@T>90p3(& z9bJjy@j0Hqy7o}uVOHIfyiQ}3tBtv}v=cEbXimWOp=WDUm7dsuH1++GIpj~X$Z>Lv z%P%fYcX#LIe357DlzuP&J$77pIq*7aROc1o|ed<&6zPx zj?UX*k|EBtnU&J`_}lui_ATnI?L|2(#VWqQ0mGG+kmunBGk;#3(V?oS5K$19_x7CR z`=`#BE~Jf1Y-Ji4mteWAJp0p7GBH?o&ix($QKI%H5v+S5FAz=W7-nIbYL`3#aslr{ z68}&VhDFFn^-2U7)|sbsgi$<98R;o^oX=c#}^gdb1gnw=ZGe8{};+i!Zy)7<7_WCMl85F@;8a zng>~Q^9Pg@uMPjEPtDjI{|F6!(R=FxsAV5=xHR}FMl3PS%+E7Ec}>g-d+kbtSgZx5 zan3ohVPxe|!-5T*cI8|JOi+x>)B`K$i|Vs}R|7STbc%U3c{x$=`a;I(6IH(}{M3*v zo-V=}fxI8muCdSmd77ngBlZVc;JCa*=-uW8D{N2+=Fn-!(w@VM1pyj?GAr~@6&>C` zeMv!jeT{BBS7CDgGq{?Rey&M+a$D{rFo6HSw{zep!L=^Hl!7K*0^y7&1Ek69wC6EP zShnuo7%XIL#-;3{^eUBI;dYY>sGt*Q}NJrpxuYKSfoK%yQ^1tW!a}3hUg|d71K}Mq2pC zGP=veUR*If1VBfW;`be@U*bGgapGh!ohFTSr*|k zox{wx>^dc943iz*C+#~!%28#$XXAU1#Jds zK6KfWr@jWlyIx5vtVLM^yJS@o7K6>BBb(c-BFd#~1};2!&WlBm=v5fLlzzDJx7D8& z*QUrBQg5QrHP5@5kITvzM4>4)SD>-ris+%*ZNR-S;0fBGGmd6JWPS4p-q~dm?NN6Z zr?F%Q%{wPwr=9EDbacT{xpU}iZ>9)VC4$xkh1r5NEV2tQ|5lQMt;EmFS}Hv&@lrC# zO#G1NNS zbw9h)+h;r~dg+U%ewhx^?-^Mfm86K3uH>zDzh2d^Qv&`(#`9iLV1 z3(}#_qP)8=`jyYL7@hNU&WmaPRr&~uf-=vwX!F=xohVl&Ucd_nyj|jT{b9N2*FwXP zNz^OD|bE;D18d#IA`;-|ugC}_5vX%Z#$KGzG(BHjlxUq-j&IHvVef8D7e z$ZA6@#yaVTsiKTXTzyb8hAF{kFC0#}E8ZD9d^6#$H@I2wah>-eRi-l-%3UqV4`i%Z zw>Fiz1ktS4r3{!^76h0hq7}XN2kGL~nyKt=_A&MPl!`a#;$k=*a6&otrPM_Z>zgb3 zSu2R)dwB+D+g`2s9FrKN-*Mm2?*#QWiE#;4^Nz+u!HjQ2d<+YZ-O2CA*hWCf`R0QemBe@0`Ig#!$M3X0~YxL$}YwBKbwbrFA zIt-U+s>>X_oFgCyE>0*!>M=fQ@0BM2vNAXNGOz-R3anbcGz;D<$W7KDVx{nW8aBR} zN#SJxjqkG>)*$ih)$x(wfbMb)aU52k7t3q#@PLZ|zlVGz9?IrrCa&J!tzC(0NVM@W zE_LYLR2$CidH7&N9W|7v?kt@{*OTK?Bq8$i3k(!n9#3#_E16H*JVf%amR(kJz=@zq z)ZT9Bxnh!xVZZ-i+RHA~9CnXm`wJG52HjgW`z>s>{D-qgw~2-oL3kfd0?T#2YF3vq z-H)!5kE-CaP>RFb`iEK?*5S&@jZ4-xcgt?q$cob(tcUoYHtCU2;&ENFK6(32=*-2~ zA!&H;R^n$fDfwEX$KJ!Y`tLD!Nw;!J3%^sCl5H|0*Lofl%V*abjfsKjEO}@pKZGo_ zaQnD>IaRVT*1uxdzF%T?i;6lPnUvZCuF^%!iHsx34ujH%mP*sQ=XFsV}w&Wv>A3T}ot=XS>qO_Fe>jsb%gFD9L@swAq5h@mjxmXhY9D-KNB>pfps(ppCF zKf$%&!@{GLM=V>Lbpn7=3+)o&N==>-5pEBrLv>C!KUaQVDfd(B?~S0ES+{-UtsV<@ zORnuh>%l+RA1}R;y|tj1nydfIpklAj*|dm7v}bnPJ{tFzJ6q^|5#oJ13yv!3?MqnF1`U8~uh~X1K!Z)5w_sR~UH@&Q1rInT z{ivOoQ~uOH`u8m1yQI+^8$ING_NkwXZPAepEX*98tV}+YT@qZ;uZlPcrdD7{sxL-v zJY{&ve1lV*Ej?J|D5oXF3Qejlv;vaGd)@??1AaZjxg2RDu4hv7_P`3BQ$%DDwO!EI zUw1#m2vJR{UX7NCv0r9h@3RGr^(6=|T%5ev{gDWM?qD^Zdz2t9+11)P>`m}kgr zBi$_FQ^W8X)Nmy)lYp&`4N$wUXilMo717qXRw z(};!Esn&&@B=P*|4ZZ-bHv&{kQ^dr zjE&y}TnG}?rg!HF<<#gQX}B^v=3AJ}!BaBmiRKN$j}oJosQZwM18;{VZ%Ul;O&-G> zI?MC=-f3U^GCFmIc7P{!w77F$ixBS_PAi3k ztHTKQxZL7HZ!HkXY1IL*CMe6d(^PGX~Cj&`Nl!GQdFU<Rtc^~Kw>Zq{od+cO$4lyN6n~vPdGHs|Tpe?VI zmAC7h?v44J8n5|7-5z>d{@I2}QCaB8S4;_;-=al0uIb*{n7(s0B4P9E@199{!*3fi z@8e>->^jjem(#nXM7Zp&oUp7fWGXx@>a`KgrG6iOHA?h<(F{xDo^U31UftKY#c${S z^UiNu=*h=>JI2H5e7@PubRXs)W6xf@5aqE*)SJbA_qaTF%Qr~gm9phoVpe%_LsIPT zzJ-bNpZNQlREZQvn|uB&be1w7vpMSzB&0T`Q9w!ox#SWg$9~001l8-6F_=ev>_AnC zP{42qTgqmvxwOa2inr?Z`nBJ?;^k8iF^7w3XtPRjV-wby%1KwTBBz4 zx&=k-OHl~Gt2eIrtC!4WPAM&zKH{25hn?+Fa2mlWO_RAf@;hv} z65ZZ~NAEFbR&QRVx9WAvT5k%znS{P>PL{*Ou2~$!evS?JBhB>uu8OEeqeq< zB$(Ri)Jq8=r=dSvelM6Tj|ajl(R(J#YNYct1}`sSIt++RuTyH_YLTxwXyk@GaKaU# zO*1e+K~jS2^7Ql4#_BK_)5-90yl|+FFtv)5q0fO|8p~MkDQH|g=sQ- z3LC=Q8JGP*GUL)PDQ{# z5GCY}29w+AwovKeFyo+AGtUG5O~Mkjmq%l!&EPTlu~<(+8hdCvccgd7mX~-7)NHl* zMZ=BOFp8s0x(SbPaG?2{)9U*ES)=jR{k6J|I8%3HWc3^k6^0eyY}KB_=H-5SJm7f_ zc0gN-Y`kBbQ~Gn_RK2(56wclX-l>rLo0_9PhVXO~>O9F2zGN+yBsvLaNhHsQWHmM; z2C|;%`(r#sHTQi^I;QE)62lT4W~*SJh%Jdkra&qs+!sr4i1;u~EmFdyjb7lqWKS1s%LH3Ba2uopPR{<7VlIUJwW7R-B~ zi2hylPif*c4(0c@5>DJ-F`hmVwuKJ~-#>z_q*D3C+G0VY7iKS|6Jq|+GPGK{iGm8J-AhwaTM)}1O(b3JS+-J^$ZFj`G(wr~^sDMEP=Bc+m=%^=R@6i9qM6v^h z9l0}hg0K4!K;a4dMY70yZWpS_W*+dTIQ9OmOVeF~P_v}{R_9mdY`ngBsOsS%1xFWa zUqI8l9iUM+qtF+xytQ(LT#Re83T-X9jo=9Olngk(NhJA_tpM|*=o^S`m<@Wya(q8s ziIqud2>;u(H<5!wwl{Te6zUG!_-(obZl$RVV#l*L@1?X7FeA~1itc=F(Im< z3y5#prGR0kWEhqeOZy1!J?t^>!S1Q`NiHTK*gYMfNj)o6AnXHUEAw#f723jJ8t{yo z*vH%d*x3zWDrz7{v4Hg1GT}}+ky0F ztl9je8k=vO2H#l8i(ER{C2E;NP2+j@?tBL+et7^=25h#xP5I0O0Nj1&jl<_Z$^AXN_e6 zHxP=TuW4AmAt0BYXJA+zjejxry1@-sP6^MEy^tG3H7pBTaT>cC*}kY0_*`xX3i%6s zk-Y)xX9cokrGx?)i8!rNnc(F*CAaI;66Y*qzSKZDxH~eWe$rIm*xN)AVBmHP`#Ny~ zFotMoA0_eE!6|^r7RzF*3}%54aHmWuU{NElHcaALIYR{sz%M3n zUn`B20CS1{E8HFswt^_)XCf(Ady0H?C0=?mXN9qe5wSv(3+V!s6~LAGW@;1xXv2Wt zZFC(|HfI3eW!l!6X7r^@(ov#1IB&Rm8$mLxjp76lz6ijdtgS!?R1DBMufTuGngc(n za)LE7xF2wN zA�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 literal 0 HcmV?d00001 From 4c5a5c1f0eddef0d384f18d46657c92e954b5075 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Jul 2014 19:34:20 +0300 Subject: [PATCH 453/488] Tests for IptcImagePlugin.py --- Tests/test_file_iptc.py | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Tests/test_file_iptc.py diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py new file mode 100644 index 000000000..4ae85c66b --- /dev/null +++ b/Tests/test_file_iptc.py @@ -0,0 +1,71 @@ +from helper import unittest, PillowTestCase, lena + +from PIL import Image, IptcImagePlugin + +TEST_FILE = "Tests/images/iptc.jpg" + + +class TestFileIptc(PillowTestCase): + + # Helpers + + def dummy_IptcImagePlugin(self): + # Create an IptcImagePlugin object without initializing it + class FakeImage: + pass + im = FakeImage() + im.__class__ = IptcImagePlugin.IptcImageFile + return im + + # Tests + + def test_getiptcinfo_jpg_none(self): + # Arrange + im = lena() + + # Act + iptc = IptcImagePlugin.getiptcinfo(im) + + # Assert + self.assertIsNone(iptc) + + def test_getiptcinfo_jpg_found(self): + # Arrange + im = Image.open(TEST_FILE) + + # Act + iptc = IptcImagePlugin.getiptcinfo(im) + + # Assert + self.assertIsInstance(iptc, dict) + self.assertEqual(iptc[(2, 90)], "Budapest") + self.assertEqual(iptc[(2, 101)], "Hungary") + + # _FIXME: is_raw() is disabled. Should we remove it? + def test__is_raw_equal_zero(self): + # Arrange + im = self.dummy_IptcImagePlugin() + offset = 0 + size = 4 + + # Act + ret = im._is_raw(offset, size) + + # Assert + self.assertEqual(ret, 0) + + def test_i(self): + # Arrange + c = "a" + + # Act + ret = IptcImagePlugin.i(c) + + # Assert + self.assertEqual(ret, 97) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 515bb6e14dc95824133cd7fac56208233880caa3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Jul 2014 19:39:11 +0300 Subject: [PATCH 454/488] flake8 --- PIL/IptcImagePlugin.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 85575612c..955afbbeb 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -21,7 +21,8 @@ __version__ = "0.3" from PIL import Image, ImageFile, _binary -import os, tempfile +import os +import tempfile i8 = _binary.i8 i16 = _binary.i16be @@ -35,17 +36,20 @@ COMPRESSION = { PAD = o8(0) * 4 + # # Helpers def i(c): return i32((PAD + c)[-4:]) + def dump(c): for i in c: print("%02x" % i8(i), end=' ') print() + ## # Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields # from TIFF and JPEG files, use the getiptcinfo function. @@ -112,7 +116,7 @@ class IptcImageFile(ImageFile.ImageFile): while True: offset = self.fp.tell() tag, size = self.field() - if not tag or tag == (8,10): + if not tag or tag == (8, 10): break if size: tagdata = self.fp.read(size) @@ -129,10 +133,10 @@ class IptcImageFile(ImageFile.ImageFile): # print tag, self.info[tag] # mode - layers = i8(self.info[(3,60)][0]) - component = i8(self.info[(3,60)][1]) - if (3,65) in self.info: - id = i8(self.info[(3,65)][0])-1 + layers = i8(self.info[(3, 60)][0]) + component = i8(self.info[(3, 60)][1]) + if (3, 65) in self.info: + id = i8(self.info[(3, 65)][0])-1 else: id = 0 if layers == 1 and not component: @@ -143,16 +147,16 @@ class IptcImageFile(ImageFile.ImageFile): self.mode = "CMYK"[id] # size - self.size = self.getint((3,20)), self.getint((3,30)) + self.size = self.getint((3, 20)), self.getint((3, 30)) # compression try: - compression = COMPRESSION[self.getint((3,120))] + compression = COMPRESSION[self.getint((3, 120))] except KeyError: raise IOError("Unknown IPTC image compression") # tile - if tag == (8,10): + if tag == (8, 10): if compression == "raw" and self._is_raw(offset, self.size): self.tile = [(compression, (offset, size + 5, -1), (0, 0, self.size[0], self.size[1]))] @@ -200,14 +204,17 @@ class IptcImageFile(ImageFile.ImageFile): im.load() self.im = im.im finally: - try: os.unlink(outfile) - except: pass + try: + os.unlink(outfile) + except: + pass Image.register_open("IPTC", IptcImageFile) Image.register_extension("IPTC", ".iim") + ## # Get IPTC information from TIFF, JPEG, or IPTC file. # @@ -267,7 +274,7 @@ def getiptcinfo(im): pass if data is None: - return None # no properties + return None # no properties # create an IptcImagePlugin object without initializing it class FakeImage: @@ -282,6 +289,6 @@ def getiptcinfo(im): try: im._open() except (IndexError, KeyError): - pass # expected failure + pass # expected failure return im.info From f5440cc3e1b0523b156a98ff311af61e003e95f9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 30 Jul 2014 20:43:34 +0300 Subject: [PATCH 455/488] Fixes for Python 3 --- PIL/IptcImagePlugin.py | 4 ++-- Tests/test_file_iptc.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 955afbbeb..a94fc7524 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -237,11 +237,11 @@ def getiptcinfo(im): # extract the IPTC/NAA resource try: app = im.app["APP13"] - if app[:14] == "Photoshop 3.0\x00": + if app[:14] == b"Photoshop 3.0\x00": app = app[14:] # parse the image resource block offset = 0 - while app[offset:offset+4] == "8BIM": + while app[offset:offset+4] == b"8BIM": offset += 4 # resource code code = JpegImagePlugin.i16(app, offset) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 4ae85c66b..fd68e88b6 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -38,8 +38,8 @@ class TestFileIptc(PillowTestCase): # Assert self.assertIsInstance(iptc, dict) - self.assertEqual(iptc[(2, 90)], "Budapest") - self.assertEqual(iptc[(2, 101)], "Hungary") + self.assertEqual(iptc[(2, 90)], b"Budapest") + self.assertEqual(iptc[(2, 101)], b"Hungary") # _FIXME: is_raw() is disabled. Should we remove it? def test__is_raw_equal_zero(self): @@ -56,7 +56,7 @@ class TestFileIptc(PillowTestCase): def test_i(self): # Arrange - c = "a" + c = b"a" # Act ret = IptcImagePlugin.i(c) From c216c6a71ecfbd1d6e3743cf28855ee781f219cd Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 31 Jul 2014 12:18:09 -0700 Subject: [PATCH 456/488] Adding coverage support for C code --- .travis.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 934d8ebf7..62fc372a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,10 @@ python: - 3.4 install: - - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick" + - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov" - "pip install cffi" - - "pip install coveralls nose" + - "pip install coveralls nose coveralls-merge" + - "gem install coveralls-lcov" - travis_retry pip install pyroma - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi @@ -31,14 +32,22 @@ install: script: - coverage erase - python setup.py clean - - python setup.py build_ext --inplace + - CFLAGS="-coverage" python setup.py build_ext --inplace - time coverage run --append --include=PIL/* selftest.py - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py after_success: + # gather the coverage data + - lcov --capture --directory . -b . --output-file coverage.info + # filter to remove system headers + - lcov --remove coverage.info '/usr/*' -o coverage.filtered.info + # convert to json + - coveralls-lcov -v -n coverage.filtered.info > coverage.c.json + - coverage report - - coveralls + - coveralls-merge coverage.c.json + - pip install pep8 pyflakes - pep8 --statistics --count PIL/*.py From a0aff1a87faa72373010d0e5c71667c7dcc34e19 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 1 Aug 2014 11:12:47 +0300 Subject: [PATCH 457/488] Remove disabled _is_raw() --- PIL/IptcImagePlugin.py | 30 ++---------------------------- Tests/test_file_iptc.py | 13 ------------- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index a94fc7524..dc8607591 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -88,28 +88,6 @@ class IptcImageFile(ImageFile.ImageFile): return tag, size - def _is_raw(self, offset, size): - # - # check if the file can be mapped - - # DISABLED: the following only slows things down... - return 0 - - self.fp.seek(offset) - t, sz = self.field() - if sz != size[0]: - return 0 - y = 1 - while True: - self.fp.seek(sz, 1) - t, s = self.field() - if t != (8, 10): - break - if s != sz: - return 0 - y += 1 - return y == size[1] - def _open(self): # load descriptive fields @@ -157,12 +135,8 @@ class IptcImageFile(ImageFile.ImageFile): # tile if tag == (8, 10): - if compression == "raw" and self._is_raw(offset, self.size): - self.tile = [(compression, (offset, size + 5, -1), - (0, 0, self.size[0], self.size[1]))] - else: - self.tile = [("iptc", (compression, offset), - (0, 0, self.size[0], self.size[1]))] + self.tile = [("iptc", (compression, offset), + (0, 0, self.size[0], self.size[1]))] def load(self): diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index fd68e88b6..3c04b7413 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -41,19 +41,6 @@ class TestFileIptc(PillowTestCase): self.assertEqual(iptc[(2, 90)], b"Budapest") self.assertEqual(iptc[(2, 101)], b"Hungary") - # _FIXME: is_raw() is disabled. Should we remove it? - def test__is_raw_equal_zero(self): - # Arrange - im = self.dummy_IptcImagePlugin() - offset = 0 - size = 4 - - # Act - ret = im._is_raw(offset, size) - - # Assert - self.assertEqual(ret, 0) - def test_i(self): # Arrange c = b"a" From 35838da803253db7ecb5e47ae635d5dcb150ccdb Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 1 Aug 2014 11:56:21 +0300 Subject: [PATCH 458/488] Test dump() --- Tests/test_file_iptc.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 3c04b7413..57c2011b6 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -51,6 +51,27 @@ class TestFileIptc(PillowTestCase): # Assert self.assertEqual(ret, 97) + def test_dump(self): + # Arrange + c = b"abc" + # Temporarily redirect stdout + try: + from cStringIO import StringIO + except ImportError: + from io import StringIO + import sys + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + + # Act + IptcImagePlugin.dump(c) + + # Reset stdout + sys.stdout = old_stdout + + # Assert + self.assertEqual(mystdout.getvalue(), b"61 62 63 \n") + if __name__ == '__main__': unittest.main() From 805184fcbb0579589cce02e86aa3fd51ad76680d Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 1 Aug 2014 12:11:03 +0300 Subject: [PATCH 459/488] Fi xffor Python 3 --- Tests/test_file_iptc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 57c2011b6..bd331e5b2 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -70,7 +70,7 @@ class TestFileIptc(PillowTestCase): sys.stdout = old_stdout # Assert - self.assertEqual(mystdout.getvalue(), b"61 62 63 \n") + self.assertEqual(mystdout.getvalue(), "61 62 63 \n") if __name__ == '__main__': From 243b59930aaff68910b9da00bfb126412fdb29b3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 1 Aug 2014 10:12:47 -0700 Subject: [PATCH 460/488] Updated Changes.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 87bc452c0..0581dc196 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -31,7 +31,7 @@ Changelog (Pillow) - Added docs for ExifTags [Wintermute3] -- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util +- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util [hugovk] - Fix return value of FreeTypeFont.textsize() does not include font offsets From e5bceac912cf9065a6079575cfb2b7ff8ffe3e8d Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Sat, 2 Aug 2014 12:17:57 +0100 Subject: [PATCH 461/488] Changed docstring to refer to the correct function --- PIL/ImageOps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py index 64c35cca1..9f84eff86 100644 --- a/PIL/ImageOps.py +++ b/PIL/ImageOps.py @@ -392,7 +392,7 @@ def solarize(image, threshold=128): """ Invert all pixel values above a threshold. - :param image: The image to posterize. + :param image: The image to solarize. :param threshold: All pixels above this greyscale level are inverted. :return: An image. """ From 6538d971e2da1032241ddeece122d1b14e0c9edc Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 2 Aug 2014 21:22:51 -0700 Subject: [PATCH 462/488] Docs for profile additions --- PIL/ImageCms.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index e708d1dad..04a3f7f61 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -150,8 +150,13 @@ for flag in FLAGS.values(): class ImageCmsProfile: def __init__(self, profile): - # accepts a string (filename), a file-like object, or a low-level - # profile object + """ + :param profile: Either a string representing a filename, + a file like object containing a profile or a + low-level profile object + + """ + if isStringType(profile): self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): @@ -170,13 +175,22 @@ class ImageCmsProfile: self.product_info = None def tobytes(self): + """ + Returns the profile in a format suitable for embedding in + saved images. + + :returns: a bytes object containing the ICC profile. + """ + return core.profile_tobytes(self.profile) class ImageCmsTransform(Image.ImagePointHandler): - """Transform. This can be used with the procedural API, or with the - standard Image.point() method. - """ + # Transform. This can be used with the procedural API, or with the + # standard Image.point() method. + # + # Will return the output profile in the output.info['icc_profile']. + def __init__(self, input, output, input_mode, output_mode, intent=INTENT_PERCEPTUAL, proof=None, @@ -576,7 +590,7 @@ def applyTransform(im, transform, inPlace=0): with the transform applied is returned (and im is not changed). The default is False. :returns: Either None, or a new PIL Image object, depending on the value of - inPlace + inPlace. The profile will be returned in the image's info['icc_profile']. :exception PyCMSError: """ From 2181ff906bf9f64d03e24ba8cf6e9af734d48390 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 3 Aug 2014 22:05:32 -0700 Subject: [PATCH 463/488] Update contributing.md [ci skip] --- CONTRIBUTING.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6f4c3f9d..71bd92ada 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,25 @@ -When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. +# Contributing + +## Fixes, Features and Changes + +Send a pull request. We'll generally want documentation and tests for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an issue or on irc (irc.freenode.net, #pil) + +- Fork the repo +- Make a branch +- Add your changes + Tests +- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable Travis-ci on your repo to catch test failures prior to the pull request. +- Push to your fork, and make a pull request. + +A few guidelines: +- Try to keep any code commits clean and separate from reformatting commits. +- All new code is going to need tests. + +## Bugs + +When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self contained scripts that pull in as few dependencies as possible. An entire django stack is harder to handle. Let us know: - - * What is the expected output? What do you see instead? - - * What versions of Pillow and Python are you using? +- What did you do? +- What did you expect to happen? +- What actually happened? +- What versions of Pillow and Python are you using? From 3ce164b59437c084a6391fa5a75d0ea173428129 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 3 Aug 2014 22:09:04 -0700 Subject: [PATCH 464/488] Update contributing.md [ci skip] --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71bd92ada..e179ce017 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ Send a pull request. We'll generally want documentation and tests for new featur A few guidelines: - Try to keep any code commits clean and separate from reformatting commits. - All new code is going to need tests. +- Try to follow PEP8. ## Bugs From 5c0395b933feb93525363ca1f247396cbf5cb825 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 4 Aug 2014 10:16:15 +0300 Subject: [PATCH 465/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0581dc196..9a554012a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,15 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Return Profile with Transformed Images #837 + [wiredfool] + +- Changed docstring to refer to the correct function #836 + [MatMoore] + +- Adding coverage support for C code tests #833 + [wiredfool] + - PyPy performance improvements #821 [wiredfool] From 7113721ad0be7bbe2df2511b490dfd201fa8e1a3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 4 Aug 2014 10:35:07 +0300 Subject: [PATCH 466/488] Update CONTRIBUTING.md [CI skip] --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e179ce017..46b897822 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,12 +2,12 @@ ## Fixes, Features and Changes -Send a pull request. We'll generally want documentation and tests for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an issue or on irc (irc.freenode.net, #pil) +Send a pull request. We'll generally want documentation and tests for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil) - Fork the repo - Make a branch - Add your changes + Tests -- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable Travis-ci on your repo to catch test failures prior to the pull request. +- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request. - Push to your fork, and make a pull request. A few guidelines: @@ -17,7 +17,7 @@ A few guidelines: ## Bugs -When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self contained scripts that pull in as few dependencies as possible. An entire django stack is harder to handle. +When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle. Let us know: - What did you do? From 368b1dce94b8d5482c19f6ad502db77a1d67740a Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 5 Aug 2014 12:27:07 +0300 Subject: [PATCH 467/488] Link to contribution guide [CI skip] --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 224f98f03..855212ce8 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Pillow *Python Imaging Library (Fork)* -Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation `_. +Pillow is the "friendly" PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation `_ and `find out how to contribute `_. .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master :target: https://travis-ci.org/python-pillow/Pillow From 408e7335b3943742c9b1eefba371ea884757c531 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 5 Aug 2014 12:35:27 +0300 Subject: [PATCH 468/488] Link to testing guide [CI skip] --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46b897822..a31c9ee09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Fixes, Features and Changes -Send a pull request. We'll generally want documentation and tests for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil) +Send a pull request. We'll generally want documentation and [tests](Tests/README.rst) for new features. Tests or documentation on their own are also welcomed. Feel free to ask questions as an [issue](https://github.com/python-pillow/Pillow/issues/new) or on IRC (irc.freenode.net, #pil) - Fork the repo - Make a branch @@ -17,7 +17,7 @@ A few guidelines: ## Bugs -When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle. +When reporting bugs, please include example code that reproduces the issue, and if possible a problem image. The best reproductions are self-contained scripts that pull in as few dependencies as possible. An entire Django stack is harder to handle. Let us know: - What did you do? From 97e111d079c23d3d287ceb627287fe3decdded90 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Thu, 7 Aug 2014 16:23:08 +0300 Subject: [PATCH 469/488] Fixed wrong mode of gif image. In case of L mode and small image. --- PIL/GifImagePlugin.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 4107c6ba3..640af9efc 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -268,10 +268,9 @@ def _save(im, fp, filename): except IOError: pass # write uncompressed file - try: - rawmode = RAWMODE[im.mode] + if im.mode in RAWMODE: imOut = im - except KeyError: + else: # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": @@ -279,10 +278,8 @@ def _save(im, fp, filename): if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 imOut = im.convert("P", palette=1, colors=palette_size) - rawmode = "P" else: imOut = im.convert("L") - rawmode = "L" # header try: @@ -290,12 +287,6 @@ def _save(im, fp, filename): except KeyError: palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) - if im.encoderinfo["optimize"]: - # When the mode is L, and we optimize, we end up with - # im.mode == P and rawmode = L, which fails. - # If we're optimizing the palette, we're going to be - # in a rawmode of P anyway. - rawmode = 'P' header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) for s in header: @@ -352,7 +343,7 @@ def _save(im, fp, filename): o8(8)) # bits imOut.encoderconfig = (8, interlace) - ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)]) + ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, RAWMODE[imOut.mode])]) fp.write(b"\0") # end of image data From 884280d0e5351ada6275c28dd9b84135601073c8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 8 Aug 2014 13:51:06 +0300 Subject: [PATCH 470/488] Update test instructions [CI skip] --- Tests/README.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/README.rst b/Tests/README.rst index 1eaa4f3a2..96aed10bc 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -3,17 +3,21 @@ Pillow Tests Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``. +Depedencies +----------- + + pip install coverage nose + + Execution --------- Run the tests from the root of the Pillow source distribution:: - python selftest.py nosetests Tests/test_*.py Or with coverage:: - coverage run --append --include=PIL/* selftest.py coverage run --append --include=PIL/* -m nose Tests/test_*.py coverage report coverage html @@ -22,3 +26,4 @@ Or with coverage:: To run an individual test:: python Tests/test_image.py + From ae641c3d0f0efef1714bfda5a856ae904e00679f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 8 Aug 2014 13:54:14 +0300 Subject: [PATCH 471/488] RST code formatting [CI skip] --- Tests/README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/README.rst b/Tests/README.rst index 96aed10bc..58c1efb9f 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -6,6 +6,8 @@ Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base c Depedencies ----------- +Install:: + pip install coverage nose From 3bde04b263d4bc45aab851722468c68049d953a2 Mon Sep 17 00:00:00 2001 From: David Cook Date: Mon, 11 Aug 2014 01:14:07 -0500 Subject: [PATCH 472/488] setup.py: Close open file handle before deleting When installing Pillow onto a Vagrant virtual machine with Linux as the guest OS, and Windows as the host OS, setup.py fails with the error "Text file busy." The temporary installation directory is a shared folder from the host OS, mounted in the guest OS, and the underlying Windows file system doesn't allow deleting the "multiarch" temporary file while a file handle for it is still open. This change closes the file handle once it is no longer being used, but before the file itself is unlinked. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e94e34d28..980519151 100644 --- a/setup.py +++ b/setup.py @@ -706,6 +706,7 @@ class pil_build_ext(build_ext): if ret >> 8 == 0: fp = open(tmpfile, 'r') multiarch_path_component = fp.readline().strip() + fp.close() _add_directory( self.compiler.library_dirs, '/usr/lib/' + multiarch_path_component) From 6eb00f605fb3b5956214c944abfa94101549c93d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 11 Aug 2014 10:05:19 +0300 Subject: [PATCH 473/488] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9a554012a..f07fcd90f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- setup.py: Close open file handle before deleting #844 + [divergentdave] + - Return Profile with Transformed Images #837 [wiredfool] From cc6610e4f3fae0e4954238b54bbf263c83c78f10 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 12 Aug 2014 16:37:49 +0300 Subject: [PATCH 474/488] Detail test-installed.py [CI skip] --- Tests/README.rst | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Tests/README.rst b/Tests/README.rst index 58c1efb9f..1b11a45b1 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -14,18 +14,32 @@ Install:: Execution --------- -Run the tests from the root of the Pillow source distribution:: - - nosetests Tests/test_*.py - -Or with coverage:: - - coverage run --append --include=PIL/* -m nose Tests/test_*.py - coverage report - coverage html - open htmlcov/index.html +**If Pillow has been built in-place** To run an individual test:: python Tests/test_image.py +Run all the tests from the root of the Pillow source distribution:: + + nosetests -vx Tests/test_*.py + +Or with coverage:: + + coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py + coverage report + coverage html + open htmlcov/index.html + +**If Pillow has been installed** + +To run an individual test:: + + ./test-installed.py Tests/test_image.py + +Run all the tests from the root of the Pillow source distribution:: + + ./test-installed.py + + + From 205e056f8f9b06ed7b925cf8aa0874bc4aaf8a7d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 6 Aug 2014 16:42:43 -0700 Subject: [PATCH 475/488] Icns DOS fix -- CVE-2014-3589 Found and reported by Andrew Drake of dropbox.com --- PIL/IcnsImagePlugin.py | 2 ++ Tests/check_icns_dos.py | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 Tests/check_icns_dos.py diff --git a/PIL/IcnsImagePlugin.py b/PIL/IcnsImagePlugin.py index 6951c9325..ca7a14931 100644 --- a/PIL/IcnsImagePlugin.py +++ b/PIL/IcnsImagePlugin.py @@ -179,6 +179,8 @@ class IcnsFile: i = HEADERSIZE while i < filesize: sig, blocksize = nextheader(fobj) + if blocksize <= 0: + raise SyntaxError('invalid block header') i += HEADERSIZE blocksize -= HEADERSIZE dct[sig] = (i, blocksize) diff --git a/Tests/check_icns_dos.py b/Tests/check_icns_dos.py new file mode 100644 index 000000000..ce6338a71 --- /dev/null +++ b/Tests/check_icns_dos.py @@ -0,0 +1,10 @@ +# Tests potential DOS of IcnsImagePlugin with 0 length block. +# Run from anywhere that PIL is importable. + +from PIL import Image +from io import BytesIO + +if bytes is str: + Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00'))) +else: + Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00', 'latin-1'))) From 2e26bd454f398131d69b49b2d090291663a18dcf Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 13 Aug 2014 09:49:50 -0700 Subject: [PATCH 476/488] Update Changes.rst [ci skip] --- CHANGES.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index f07fcd90f..6539c5d11 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin + [Andrew Drake] + - setup.py: Close open file handle before deleting #844 [divergentdave] @@ -57,7 +60,14 @@ Changelog (Pillow) - Test PalmImagePlugin and method to skip known bad tests #776 [hugovk, wiredfool] - + + 2.5.2 (2014-08-13) +------------------ + +- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport) + [Andrew Drake] + + 2.5.1 (2014-07-10) ------------------ @@ -286,6 +296,12 @@ Changelog (Pillow) - Prefer homebrew freetype over X11 freetype (but still allow both) [dmckeone] +2.3.2 (2014-08-13) +------------------ + +- Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport) + [Andrew Drake] + 2.3.1 (2014-03-14) ------------------ From e51b7ad7bf7ccdb241c14990ec5ba3c7d1992de6 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 13 Aug 2014 09:51:05 -0700 Subject: [PATCH 477/488] Updated Changes.rst [ci skip] formatting --- CHANGES.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6539c5d11..141d5c715 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -61,7 +61,8 @@ Changelog (Pillow) - Test PalmImagePlugin and method to skip known bad tests #776 [hugovk, wiredfool] - 2.5.2 (2014-08-13) + +2.5.2 (2014-08-13) ------------------ - Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin (backport) From 99cb19b671a4d0a76b845d090b1a491e9cb5e732 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 15 Aug 2014 13:58:54 +0300 Subject: [PATCH 478/488] Remove a rogue "=" from the end of a line It wasn't there in the original PIL handbook: http://effbot.org/imagingbook/format-jpeg.htm [CI skip] --- docs/handbook/image-file-formats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 8c9bb36ec..97caea722 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -136,7 +136,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: **quality** The image quality, on a scale from 1 (worst) to 95 (best). The default is 75. Values above 95 should be avoided; 100 disables portions of the JPEG - compression algorithm, and results in large files with hardly any gain in = + compression algorithm, and results in large files with hardly any gain in image quality. **optimize** From ce89d19691ca15b954c69235f6c1b1cc56bd642e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 16 Aug 2014 18:45:36 +0300 Subject: [PATCH 479/488] No need to time now Travis has time tags http://blog.travis-ci.com/2014-08-13-per-command-time-tags --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62fc372a5..4d6a6fba9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ env: MAX_CONCURRENCY=4 python: - "pypy" - - "pypy3" + - "pypy3"t - 2.6 - 2.7 - "2.7_with_system_site_packages" # For PyQt4 @@ -34,7 +34,7 @@ script: - python setup.py clean - CFLAGS="-coverage" python setup.py build_ext --inplace - - time coverage run --append --include=PIL/* selftest.py + - coverage run --append --include=PIL/* selftest.py - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py after_success: From 30979d4dd4bd9b88e1cbcef307a585b37c76ef4e Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 17 Aug 2014 23:34:12 +0300 Subject: [PATCH 480/488] Fix typo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4d6a6fba9..7635334a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ env: MAX_CONCURRENCY=4 python: - "pypy" - - "pypy3"t + - "pypy3" - 2.6 - 2.7 - "2.7_with_system_site_packages" # For PyQt4 From 81e3379834141cb742b8b9137b89590213fca833 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Aug 2014 10:53:31 +0300 Subject: [PATCH 481/488] Link to CHANGES from README And use absolute URLs for GitHub links so they work from PyPI. [CI skip] --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 855212ce8..482a1e2ec 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Pillow *Python Imaging Library (Fork)* -Pillow is the "friendly" PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation `_ and `find out how to contribute `_. +Pillow is the "friendly" PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. For more information, please `read the documentation `_, `check the changelog `_ and `find out how to contribute `_. .. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master :target: https://travis-ci.org/python-pillow/Pillow From b56043c036a7a89db13ee6e80c4769dbefc1b9b9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 19 Aug 2014 11:03:08 +0300 Subject: [PATCH 482/488] Remove 2,505-line changelog from description It made the PyPI listing page very long and requires a lot of scrolling to get down to the files, annoying for downstream packagers. Instead it's linked from the README. [CI skip] --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 980519151..ac3bc3ea8 100644 --- a/setup.py +++ b/setup.py @@ -721,9 +721,7 @@ setup( name=NAME, version=PILLOW_VERSION, description='Python Imaging Library (Fork)', - long_description=( - _read('README.rst') + b'\n' + - _read('CHANGES.rst')).decode('utf-8'), + long_description=_read('README.rst').decode('utf-8'), author='Alex Clark (fork author)', author_email='aclark@aclark.net', url='http://python-pillow.github.io/', From 9604cf814bdb210a1ed2e684e2910875a40df6c7 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 19 Aug 2014 12:24:44 +0300 Subject: [PATCH 483/488] Added test case for gif image (mode L): optimization turned on, but not needed. --- Tests/test_file_gif.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 93b826fd6..0111d3965 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -35,6 +35,14 @@ class TestFileGif(PillowTestCase): self.assertEqual(test(0), 800) self.assertEqual(test(1), 38) + def test_optimize_full_L(self): + from io import BytesIO + + im = Image.frombytes("L", (16, 16), bytes(range(256))) + file = BytesIO() + im.save(file, "GIF", optimize=optimize) + self.assertEqual(im.mode, "L") + def test_roundtrip(self): out = self.tempfile('temp.gif') im = lena() From 78081a24989469ef43d3b5d31265ab0c73f80d08 Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 19 Aug 2014 12:32:52 +0300 Subject: [PATCH 484/488] Fixed test_optimize_full_l gif file test case. --- Tests/test_file_gif.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 0111d3965..12e12b2f3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -35,12 +35,12 @@ class TestFileGif(PillowTestCase): self.assertEqual(test(0), 800) self.assertEqual(test(1), 38) - def test_optimize_full_L(self): + def test_optimize_full_l(self): from io import BytesIO im = Image.frombytes("L", (16, 16), bytes(range(256))) file = BytesIO() - im.save(file, "GIF", optimize=optimize) + im.save(file, "GIF", optimize=True) self.assertEqual(im.mode, "L") def test_roundtrip(self): From 778768c9bc333ecba88b1080512e2b384c6f5b3c Mon Sep 17 00:00:00 2001 From: Nikita Uvarov Date: Tue, 19 Aug 2014 15:00:15 +0300 Subject: [PATCH 485/488] Fixed test_optimize_full_l test case for python2. --- Tests/test_file_gif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 12e12b2f3..bd4a6e76c 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -38,7 +38,7 @@ class TestFileGif(PillowTestCase): def test_optimize_full_l(self): from io import BytesIO - im = Image.frombytes("L", (16, 16), bytes(range(256))) + im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) file = BytesIO() im.save(file, "GIF", optimize=True) self.assertEqual(im.mode, "L") From 735d45b17574f575043eb16abeb5f8a205b0e8ab Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 19 Aug 2014 08:52:24 -0400 Subject: [PATCH 486/488] Fix MANIFEST include *.md recursive-include Scripts *.sh recursive-include Tests *.bw recursive-include Tests *.cur recursive-include Tests *.dcx recursive-include Tests *.mpo recursive-include Tests *.ras recursive-include Tests *.rgb recursive-include Tests *.sgi --- MANIFEST.in | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 00c132d47..643e8056c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,7 @@ + include *.c include *.h +include *.md include *.py include *.rst include *.txt @@ -25,14 +27,20 @@ recursive-include Images *.xpm recursive-include PIL *.md recursive-include Sane *.c recursive-include Sane *.py +recursive-include Sane *.rst recursive-include Sane *.txt recursive-include Sane CHANGES recursive-include Sane README recursive-include Scripts *.py +recursive-include Scripts *.rst +recursive-include Scripts *.sh recursive-include Scripts README recursive-include Tests *.bdf recursive-include Tests *.bin recursive-include Tests *.bmp +recursive-include Tests *.bw +recursive-include Tests *.cur +recursive-include Tests *.dcx recursive-include Tests *.doc recursive-include Tests *.eps recursive-include Tests *.fli @@ -46,6 +54,7 @@ recursive-include Tests *.j2k recursive-include Tests *.jp2 recursive-include Tests *.jpg recursive-include Tests *.lut +recursive-include Tests *.mpo recursive-include Tests *.pbm recursive-include Tests *.pcf recursive-include Tests *.pcx @@ -55,7 +64,10 @@ recursive-include Tests *.png recursive-include Tests *.ppm recursive-include Tests *.psd recursive-include Tests *.py +recursive-include Tests *.ras +recursive-include Tests *.rgb recursive-include Tests *.rst +recursive-include Tests *.sgi recursive-include Tests *.spider recursive-include Tests *.tar recursive-include Tests *.tif @@ -65,22 +77,20 @@ recursive-include Tests *.txt recursive-include Tests *.webp recursive-include Tests *.xpm recursive-include Tk *.c -recursive-include Tk *.txt recursive-include Tk *.rst -recursive-include depends *.sh +recursive-include Tk *.txt recursive-include depends *.rst +recursive-include depends *.sh recursive-include docs *.bat recursive-include docs *.gitignore recursive-include docs *.html recursive-include docs *.py recursive-include docs *.rst recursive-include docs *.txt -recursive-include docs Guardfile -recursive-include docs Makefile recursive-include docs BUILDME recursive-include docs COPYING +recursive-include docs Guardfile recursive-include docs LICENSE +recursive-include docs Makefile recursive-include libImaging *.c recursive-include libImaging *.h -recursive-include Sane *.rst -recursive-include Scripts *.rst From 347a1d8d956f9e64af4463ee25311b60cdd5657d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 12 Aug 2014 12:31:37 -0700 Subject: [PATCH 487/488] J2k DOS fix -- CVE-2014-3598 Found and reported by Andrew Drake of dropbox.com --- PIL/Jpeg2KImagePlugin.py | 3 +++ Tests/check_j2k_dos.py | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 Tests/check_j2k_dos.py diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index 0a7a6e297..53b10ca1a 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -70,6 +70,9 @@ def _parse_jp2_header(fp): else: hlen = 8 + if lbox < hlen: + raise SyntaxError('Invalid JP2 header length') + if tbox == b'jp2h': header = fp.read(lbox - hlen) break diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py new file mode 100644 index 000000000..68f065bbc --- /dev/null +++ b/Tests/check_j2k_dos.py @@ -0,0 +1,11 @@ +# Tests potential DOS of Jpeg2kImagePlugin with 0 length block. +# Run from anywhere that PIL is importable. + +from PIL import Image +from io import BytesIO + +if bytes is str: + Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) +else: + Image.open(BytesIO(bytes('\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang', 'latin-1'))) + From 2d634d3019472ce09fb0aadbb4ec4d20fd8d7d47 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 12 Aug 2014 12:40:15 -0700 Subject: [PATCH 488/488] Bump Version/Changelog --- CHANGES.rst | 9 +++++++++ PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 141d5c715..47df8c176 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.6.0 (unreleased) ------------------ +- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport) + [Andrew Drake] + - Fixed CVE-2014-3589, a DOS in the IcnsImagePlugin [Andrew Drake] @@ -61,6 +64,12 @@ Changelog (Pillow) - Test PalmImagePlugin and method to skip known bad tests #776 [hugovk, wiredfool] +2.5.3 (2014-08-18) +------------------ + +- Fixed CVE-2014-3598, a DOS in the Jpeg2KImagePlugin (backport) + [Andrew Drake] + 2.5.2 (2014-08-13) ------------------ diff --git a/PIL/__init__.py b/PIL/__init__.py index 56edaf247..7b4b8abfa 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.5.0' # Pillow +PILLOW_VERSION = '2.5.3' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index c28bd4d93..ec8205dd4 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.5.0" +#define PILLOW_VERSION "2.5.3" #include "Python.h" diff --git a/setup.py b/setup.py index ac3bc3ea8..5cf0e5e65 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.5.0' +PILLOW_VERSION = '2.5.3' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None