mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-03 21:24:31 +03:00
Merge pull request #2382 from hugovk/test-effects
Test linear and radial gradient effects
This commit is contained in:
commit
9c4eafc188
53
PIL/Image.py
53
PIL/Image.py
|
@ -144,6 +144,7 @@ def isImageType(t):
|
|||
"""
|
||||
return hasattr(t, "im")
|
||||
|
||||
|
||||
#
|
||||
# Constants (also defined in _imagingmodule.c!)
|
||||
|
||||
|
@ -339,6 +340,7 @@ def getmodebands(mode):
|
|||
"""
|
||||
return len(ImageMode.getmode(mode).bands)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
|
@ -691,7 +693,7 @@ class Image(object):
|
|||
return b"".join(data)
|
||||
|
||||
def tostring(self, *args, **kw):
|
||||
raise NotImplementedError("tostring() has been removed. " +
|
||||
raise NotImplementedError("tostring() has been removed. "
|
||||
"Please call tobytes() instead.")
|
||||
|
||||
def tobitmap(self, name="image"):
|
||||
|
@ -742,7 +744,7 @@ class Image(object):
|
|||
raise ValueError("cannot decode image data")
|
||||
|
||||
def fromstring(self, *args, **kw):
|
||||
raise NotImplementedError("fromstring() has been removed. " +
|
||||
raise NotImplementedError("fromstring() has been removed. "
|
||||
"Please call frombytes() instead.")
|
||||
|
||||
def load(self):
|
||||
|
@ -879,7 +881,7 @@ class Image(object):
|
|||
try:
|
||||
t = trns_im.palette.getcolor(t)
|
||||
except:
|
||||
raise ValueError("Couldn't allocate a palette " +
|
||||
raise ValueError("Couldn't allocate a palette "
|
||||
"color for transparency")
|
||||
trns_im.putpixel((0, 0), t)
|
||||
|
||||
|
@ -1036,7 +1038,6 @@ class Image(object):
|
|||
|
||||
return self._new(self.im.crop((x0, y0, x1, y1)))
|
||||
|
||||
|
||||
def draft(self, mode, size):
|
||||
"""
|
||||
Configures the image file loader so it returns a version of the
|
||||
|
@ -1254,7 +1255,7 @@ class Image(object):
|
|||
return self.im.histogram()
|
||||
|
||||
def offset(self, xoffset, yoffset=None):
|
||||
raise NotImplementedError("offset() has been removed. " +
|
||||
raise NotImplementedError("offset() has been removed. "
|
||||
"Please call ImageChops.offset() instead.")
|
||||
|
||||
def paste(self, im, box=None, mask=None):
|
||||
|
@ -1553,7 +1554,8 @@ class Image(object):
|
|||
|
||||
return self._new(self.im.resize(size, resample))
|
||||
|
||||
def rotate(self, angle, resample=NEAREST, expand=0, center=None, translate=None):
|
||||
def rotate(self, angle, resample=NEAREST, expand=0, center=None,
|
||||
translate=None):
|
||||
"""
|
||||
Returns a rotated copy of this image. This method returns a
|
||||
copy of this image, rotated the given number of degrees counter
|
||||
|
@ -1622,10 +1624,13 @@ class Image(object):
|
|||
round(math.cos(angle), 15), round(math.sin(angle), 15), 0.0,
|
||||
round(-math.sin(angle), 15), round(math.cos(angle), 15), 0.0
|
||||
]
|
||||
|
||||
def transform(x, y, matrix):
|
||||
(a, b, c, d, e, f) = matrix
|
||||
return a*x + b*y + c, d*x + e*y + f
|
||||
matrix[2], matrix[5] = transform(-center[0] - translate[0], -center[1] - translate[1], matrix)
|
||||
|
||||
matrix[2], matrix[5] = transform(-center[0] - translate[0],
|
||||
-center[1] - translate[1], matrix)
|
||||
matrix[2] += center[0]
|
||||
matrix[5] += center[1]
|
||||
|
||||
|
@ -1641,9 +1646,11 @@ class Image(object):
|
|||
nh = int(math.ceil(max(yy)) - math.floor(min(yy)))
|
||||
|
||||
# We multiply a translation matrix from the right. Because of its
|
||||
# special form, this is the same as taking the image of the translation vector
|
||||
# as new translation vector.
|
||||
matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix)
|
||||
# special form, this is the same as taking the image of the
|
||||
# translation vector as new translation vector.
|
||||
matrix[2], matrix[5] = transform(-(nw - w) / 2.0,
|
||||
-(nh - h) / 2.0,
|
||||
matrix)
|
||||
w, h = nw, nh
|
||||
|
||||
return self.transform((w, h), AFFINE, matrix, resample)
|
||||
|
@ -1759,8 +1766,8 @@ class Image(object):
|
|||
PPM file, and calls either the **xv** utility or the **display**
|
||||
utility, depending on which one can be found.
|
||||
|
||||
On macOS, this method saves the image to a temporary BMP file, and opens
|
||||
it with the native Preview application.
|
||||
On macOS, this method saves the image to a temporary BMP file, and
|
||||
opens it with the native Preview application.
|
||||
|
||||
On Windows, it saves the image to a temporary BMP file, and uses
|
||||
the standard BMP display utility to show it (usually Paint).
|
||||
|
@ -1988,7 +1995,6 @@ class Image(object):
|
|||
return ImageQt.toqpixmap(self)
|
||||
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Abstract handlers.
|
||||
|
||||
|
@ -2013,6 +2019,7 @@ def _wedge():
|
|||
|
||||
return Image()._new(core.wedge("L"))
|
||||
|
||||
|
||||
def _check_size(size):
|
||||
"""
|
||||
Common check to enforce type and sanity check on size tuples
|
||||
|
@ -2030,6 +2037,7 @@ def _check_size(size):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
def new(mode, size, color=0):
|
||||
"""
|
||||
Creates a new image with the given mode and size.
|
||||
|
@ -2230,6 +2238,7 @@ def fromqpixmap(im):
|
|||
raise ImportError("Qt bindings are not installed")
|
||||
return ImageQt.fromqpixmap(im)
|
||||
|
||||
|
||||
_fromarray_typemap = {
|
||||
# (shape, typestr) => mode, rawmode
|
||||
# first two members of shape are set to one
|
||||
|
@ -2558,3 +2567,21 @@ def effect_noise(size, sigma):
|
|||
:param sigma: Standard deviation of noise.
|
||||
"""
|
||||
return Image()._new(core.effect_noise(size, sigma))
|
||||
|
||||
|
||||
def linear_gradient(mode):
|
||||
"""
|
||||
Generate 256x256 linear gradient from black to white, top to bottom.
|
||||
|
||||
:param mode: Input mode.
|
||||
"""
|
||||
return Image()._new(core.linear_gradient(mode))
|
||||
|
||||
|
||||
def radial_gradient(mode):
|
||||
"""
|
||||
Generate 256x256 radial gradient from black to white, centre to edge.
|
||||
|
||||
:param mode: Input mode.
|
||||
"""
|
||||
return Image()._new(core.radial_gradient(mode))
|
||||
|
|
BIN
Tests/images/linear_gradient.png
Normal file
BIN
Tests/images/linear_gradient.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 516 B |
BIN
Tests/images/radial_gradient.png
Normal file
BIN
Tests/images/radial_gradient.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
|
@ -280,7 +280,8 @@ class TestImage(PillowTestCase):
|
|||
self.assert_image_similar(im2, im3, 110)
|
||||
|
||||
def test_check_size(self):
|
||||
# Checking that the _check_size function throws value errors when we want it to.
|
||||
# Checking that the _check_size function throws value errors
|
||||
# when we want it to.
|
||||
with self.assertRaises(ValueError):
|
||||
Image.new('RGB', 0) # not a tuple
|
||||
with self.assertRaises(ValueError):
|
||||
|
@ -316,6 +317,58 @@ class TestImage(PillowTestCase):
|
|||
def test_fromstring(self):
|
||||
self.assertRaises(NotImplementedError, Image.fromstring)
|
||||
|
||||
def test_linear_gradient_wrong_mode(self):
|
||||
# Arrange
|
||||
wrong_mode = "RGB"
|
||||
|
||||
# Act / Assert
|
||||
self.assertRaises(ValueError,
|
||||
lambda: Image.linear_gradient(wrong_mode))
|
||||
return
|
||||
|
||||
def test_linear_gradient(self):
|
||||
|
||||
# Arrange
|
||||
target_file = "Tests/images/linear_gradient.png"
|
||||
for mode in ["L", "P"]:
|
||||
|
||||
# Act
|
||||
im = Image.linear_gradient(mode)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(im.size, (256, 256))
|
||||
self.assertEqual(im.mode, mode)
|
||||
self.assertEqual(im.getpixel((0, 0)), 0)
|
||||
self.assertEqual(im.getpixel((255, 255)), 255)
|
||||
target = Image.open(target_file).convert(mode)
|
||||
self.assert_image_equal(im, target)
|
||||
|
||||
def test_radial_gradient_wrong_mode(self):
|
||||
# Arrange
|
||||
wrong_mode = "RGB"
|
||||
|
||||
# Act / Assert
|
||||
self.assertRaises(ValueError,
|
||||
lambda: Image.radial_gradient(wrong_mode))
|
||||
return
|
||||
|
||||
def test_radial_gradient(self):
|
||||
|
||||
# Arrange
|
||||
target_file = "Tests/images/radial_gradient.png"
|
||||
for mode in ["L", "P"]:
|
||||
|
||||
# Act
|
||||
im = Image.radial_gradient(mode)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(im.size, (256, 256))
|
||||
self.assertEqual(im.mode, mode)
|
||||
self.assertEqual(im.getpixel((0, 0)), 255)
|
||||
self.assertEqual(im.getpixel((128, 128)), 0)
|
||||
target = Image.open(target_file).convert(mode)
|
||||
self.assert_image_equal(im, target)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -64,15 +64,18 @@ ImagingFillLinearGradient(const char *mode)
|
|||
Imaging im;
|
||||
int y;
|
||||
|
||||
if (strlen(mode) != 1)
|
||||
if (strlen(mode) != 1) {
|
||||
return (Imaging) ImagingError_ModeError();
|
||||
}
|
||||
|
||||
im = ImagingNew(mode, 256, 256);
|
||||
if (!im)
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (y = 0; y < 256; y++)
|
||||
for (y = 0; y < 256; y++) {
|
||||
memset(im->image8[y], (unsigned char) y, 256);
|
||||
}
|
||||
|
||||
return im;
|
||||
}
|
||||
|
@ -84,21 +87,25 @@ ImagingFillRadialGradient(const char *mode)
|
|||
int x, y;
|
||||
int d;
|
||||
|
||||
if (strlen(mode) != 1)
|
||||
if (strlen(mode) != 1) {
|
||||
return (Imaging) ImagingError_ModeError();
|
||||
}
|
||||
|
||||
im = ImagingNew(mode, 256, 256);
|
||||
if (!im)
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (y = 0; y < 256; y++)
|
||||
for (y = 0; y < 256; y++) {
|
||||
for (x = 0; x < 256; x++) {
|
||||
d = (int) sqrt((double) ((x-128)*(x-128) + (y-128)*(y-128)) * 2.0);
|
||||
if (d >= 255)
|
||||
if (d >= 255) {
|
||||
im->image8[y][x] = 255;
|
||||
else
|
||||
} else {
|
||||
im->image8[y][x] = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return im;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user