diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index a7e39a499..f88cfc2f6 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -64,6 +64,43 @@ class TestImagePutPixel(AccessTest): self.assert_image_equal(im1, im2) + def test_sanity_negative_index(self): + im1 = hopper() + im2 = Image.new(im1.mode, im1.size, 0) + + width, heigth = im1.size + self.assertEqual(im1.getpixel((0, 0)), im1.getpixel((-width, -heigth))) + self.assertEqual(im1.getpixel((-1, -1)), + im1.getpixel((width-1, heigth-1))) + + for y in range(-1, -im1.size[1]-1, -1): + for x in range(-1, -im1.size[0]-1, -1): + 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(-1, -im1.size[1]-1, -1): + for x in range(-1, -im1.size[0]-1, -1): + 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(-1, -im1.size[1]-1, -1): + for x in range(-1, -im1.size[0]-1, -1): + pix2[x, y] = pix1[x, y] + + self.assert_image_equal(im1, im2) class TestImageGetPixel(AccessTest): @staticmethod @@ -85,23 +122,43 @@ class TestImageGetPixel(AccessTest): im.getpixel((0, 0)), c, "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c)) + # check putpixel negative index + im.putpixel((-1, -1), c) + self.assertEqual( + im.getpixel((-1, -1)), c, + "put/getpixel roundtrip negative index failed" + " for mode %s, color %s" % (mode, c)) + # Check 0 im = Image.new(mode, (0, 0), None) with self.assertRaises(IndexError): im.putpixel((0, 0), c) with self.assertRaises(IndexError): im.getpixel((0, 0)) + # Check 0 negative index + with self.assertRaises(IndexError): + im.putpixel((-1, -1), c) + with self.assertRaises(IndexError): + im.getpixel((-1, -1)) # check initial color im = Image.new(mode, (1, 1), c) self.assertEqual( im.getpixel((0, 0)), c, "initial color failed for mode %s, color %s " % (mode, c)) + # check initial color negative index + self.assertEqual( + im.getpixel((-1, -1)), c, + "initial color failed with negative index" + "for mode %s, color %s " % (mode, c)) # Check 0 im = Image.new(mode, (0, 0), c) with self.assertRaises(IndexError): im.getpixel((0, 0)) + # Check 0 negative index + with self.assertRaises(IndexError): + im.getpixel((-1, -1)) def test_basic(self): for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 395dbcd3b..adbd6132d 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -74,7 +74,12 @@ class PyAccess(object): """ if self.readonly: raise ValueError('Attempt to putpixel a read only image') - (x, y) = self.check_xy(xy) + (x, y) = xy + if x < 0: + x = self.xsize + x + if y < 0: + y = self.ysize + y + (x, y) = self.check_xy((x, y)) return self.set_pixel(x, y, color) def __getitem__(self, xy): @@ -89,9 +94,9 @@ class PyAccess(object): pixel values for multiband images. """ (x, y) = xy - if (x < 0): + if x < 0: x = self.xsize + x - if (y < 0): + if y < 0: y = self.ysize + y (x, y) = self.check_xy((x, y)) return self.get_pixel(x, y) diff --git a/src/_imaging.c b/src/_imaging.c index ede531e03..474444348 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -471,12 +471,14 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) INT32 i; FLOAT32 f; } pixel; - if (x < 0){ + + if (x < 0) { x = im->xsize + x; } - if (y < 0){ + if (y < 0) { y = im->ysize + y; } + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { PyErr_SetString(PyExc_IndexError, outside_image); return NULL; @@ -1654,6 +1656,13 @@ _putpixel(ImagingObject* self, PyObject* args) im = self->image; + if (x < 0) { + x = im->xsize + x; + } + if (y < 0) { + y = im->ysize + y; + } + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { PyErr_SetString(PyExc_IndexError, outside_image); return NULL; @@ -3019,6 +3028,13 @@ pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) if (_getxy(xy, &x, &y)) return -1; + if (x < 0) { + x = im->xsize + x; + } + if (y < 0) { + y = im->ysize + y; + } + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { PyErr_SetString(PyExc_IndexError, outside_image); return -1;