mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-14 05:36:48 +03:00
Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii
This commit is contained in:
parent
08b538780d
commit
c167d7a269
|
@ -22,7 +22,7 @@ def test_imageops_box_blur():
|
||||||
|
|
||||||
|
|
||||||
def box_blur(image, radius=1, n=1):
|
def box_blur(image, radius=1, n=1):
|
||||||
return image._new(image.im.box_blur(radius, n))
|
return image._new(image.im.box_blur((radius, radius), n))
|
||||||
|
|
||||||
|
|
||||||
def assert_image(im, data, delta=0):
|
def assert_image(im, data, delta=0):
|
||||||
|
|
|
@ -24,8 +24,10 @@ from .helper import assert_image_equal, hopper
|
||||||
ImageFilter.ModeFilter,
|
ImageFilter.ModeFilter,
|
||||||
ImageFilter.GaussianBlur,
|
ImageFilter.GaussianBlur,
|
||||||
ImageFilter.GaussianBlur(5),
|
ImageFilter.GaussianBlur(5),
|
||||||
|
ImageFilter.GaussianBlur((2, 5)),
|
||||||
ImageFilter.BoxBlur(0),
|
ImageFilter.BoxBlur(0),
|
||||||
ImageFilter.BoxBlur(5),
|
ImageFilter.BoxBlur(5),
|
||||||
|
ImageFilter.BoxBlur((2, 5)),
|
||||||
ImageFilter.UnsharpMask,
|
ImageFilter.UnsharpMask,
|
||||||
ImageFilter.UnsharpMask(10),
|
ImageFilter.UnsharpMask(10),
|
||||||
),
|
),
|
||||||
|
@ -185,12 +187,21 @@ def test_consistency_5x5(mode):
|
||||||
assert_image_equal(source.filter(kernel), reference)
|
assert_image_equal(source.filter(kernel), reference)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_box_blur_filter():
|
@pytest.mark.parametrize(
|
||||||
|
"radius",
|
||||||
|
(
|
||||||
|
-2,
|
||||||
|
(-2, -2),
|
||||||
|
(-2, 2),
|
||||||
|
(2, -2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_invalid_box_blur_filter(radius):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageFilter.BoxBlur(-2)
|
ImageFilter.BoxBlur(radius)
|
||||||
|
|
||||||
im = hopper()
|
im = hopper()
|
||||||
box_blur_filter = ImageFilter.BoxBlur(2)
|
box_blur_filter = ImageFilter.BoxBlur(2)
|
||||||
box_blur_filter.radius = -2
|
box_blur_filter.radius = radius
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.filter(box_blur_filter)
|
im.filter(box_blur_filter)
|
||||||
|
|
|
@ -157,7 +157,8 @@ class GaussianBlur(MultibandFilter):
|
||||||
approximates a Gaussian kernel. For details on accuracy see
|
approximates a Gaussian kernel. For details on accuracy see
|
||||||
<https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
|
<https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf>
|
||||||
|
|
||||||
:param radius: Standard deviation of the Gaussian kernel.
|
:param radius: Standard deviation of the Gaussian kernel. Either a sequence of two
|
||||||
|
numbers for x and y, or a single number for both.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "GaussianBlur"
|
name = "GaussianBlur"
|
||||||
|
@ -166,7 +167,10 @@ class GaussianBlur(MultibandFilter):
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
|
|
||||||
def filter(self, image):
|
def filter(self, image):
|
||||||
return image.gaussian_blur(self.radius)
|
xy = self.radius
|
||||||
|
if not isinstance(xy, (tuple, list)):
|
||||||
|
xy = (xy, xy)
|
||||||
|
return image.gaussian_blur(xy)
|
||||||
|
|
||||||
|
|
||||||
class BoxBlur(MultibandFilter):
|
class BoxBlur(MultibandFilter):
|
||||||
|
@ -176,21 +180,29 @@ class BoxBlur(MultibandFilter):
|
||||||
which runs in linear time relative to the size of the image
|
which runs in linear time relative to the size of the image
|
||||||
for any radius value.
|
for any radius value.
|
||||||
|
|
||||||
:param radius: Size of the box in one direction. Radius 0 does not blur,
|
:param radius: Size of the box in a direction. Either a sequence of two numbers for
|
||||||
returns an identical image. Radius 1 takes 1 pixel
|
x and y, or a single number for both.
|
||||||
in each direction, i.e. 9 pixels in total.
|
|
||||||
|
Radius 0 does not blur, returns an identical image.
|
||||||
|
Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "BoxBlur"
|
name = "BoxBlur"
|
||||||
|
|
||||||
def __init__(self, radius):
|
def __init__(self, radius):
|
||||||
if radius < 0:
|
xy = radius
|
||||||
|
if not isinstance(xy, (tuple, list)):
|
||||||
|
xy = (xy, xy)
|
||||||
|
if xy[0] < 0 or xy[1] < 0:
|
||||||
msg = "radius must be >= 0"
|
msg = "radius must be >= 0"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
self.radius = radius
|
self.radius = radius
|
||||||
|
|
||||||
def filter(self, image):
|
def filter(self, image):
|
||||||
return image.box_blur(self.radius)
|
xy = self.radius
|
||||||
|
if not isinstance(xy, (tuple, list)):
|
||||||
|
xy = (xy, xy)
|
||||||
|
return image.box_blur(xy)
|
||||||
|
|
||||||
|
|
||||||
class UnsharpMask(MultibandFilter):
|
class UnsharpMask(MultibandFilter):
|
||||||
|
|
|
@ -1075,9 +1075,9 @@ _gaussian_blur(ImagingObject *self, PyObject *args) {
|
||||||
Imaging imIn;
|
Imaging imIn;
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
|
|
||||||
float radius = 0;
|
float xradius, yradius;
|
||||||
int passes = 3;
|
int passes = 3;
|
||||||
if (!PyArg_ParseTuple(args, "f|i", &radius, &passes)) {
|
if (!PyArg_ParseTuple(args, "(ff)|i", &xradius, &yradius, &passes)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1087,7 +1087,7 @@ _gaussian_blur(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ImagingGaussianBlur(imOut, imIn, radius, passes)) {
|
if (!ImagingGaussianBlur(imOut, imIn, xradius, yradius, passes)) {
|
||||||
ImagingDelete(imOut);
|
ImagingDelete(imOut);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -2131,9 +2131,9 @@ _box_blur(ImagingObject *self, PyObject *args) {
|
||||||
Imaging imIn;
|
Imaging imIn;
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
|
|
||||||
float radius;
|
float xradius, yradius;
|
||||||
int n = 1;
|
int n = 1;
|
||||||
if (!PyArg_ParseTuple(args, "f|i", &radius, &n)) {
|
if (!PyArg_ParseTuple(args, "(ff)|i", &xradius, &yradius, &n)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2143,7 +2143,7 @@ _box_blur(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ImagingBoxBlur(imOut, imIn, radius, n)) {
|
if (!ImagingBoxBlur(imOut, imIn, xradius, yradius, n)) {
|
||||||
ImagingDelete(imOut);
|
ImagingDelete(imOut);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,14 +230,14 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
|
ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) {
|
||||||
int i;
|
int i;
|
||||||
Imaging imTransposed;
|
Imaging imTransposed;
|
||||||
|
|
||||||
if (n < 1) {
|
if (n < 1) {
|
||||||
return ImagingError_ValueError("number of passes must be greater than zero");
|
return ImagingError_ValueError("number of passes must be greater than zero");
|
||||||
}
|
}
|
||||||
if (radius < 0) {
|
if (xradius < 0 || yradius < 0) {
|
||||||
return ImagingError_ValueError("radius must be >= 0");
|
return ImagingError_ValueError("radius must be >= 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,16 +266,16 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
|
||||||
/* Apply blur in one dimension.
|
/* Apply blur in one dimension.
|
||||||
Use imOut as a destination at first pass,
|
Use imOut as a destination at first pass,
|
||||||
then use imOut as a source too. */
|
then use imOut as a source too. */
|
||||||
ImagingHorizontalBoxBlur(imOut, imIn, radius);
|
ImagingHorizontalBoxBlur(imOut, imIn, xradius);
|
||||||
for (i = 1; i < n; i++) {
|
for (i = 1; i < n; i++) {
|
||||||
ImagingHorizontalBoxBlur(imOut, imOut, radius);
|
ImagingHorizontalBoxBlur(imOut, imOut, xradius);
|
||||||
}
|
}
|
||||||
/* Transpose result for blur in another direction. */
|
/* Transpose result for blur in another direction. */
|
||||||
ImagingTranspose(imTransposed, imOut);
|
ImagingTranspose(imTransposed, imOut);
|
||||||
|
|
||||||
/* Reuse imTransposed as a source and destination there. */
|
/* Reuse imTransposed as a source and destination there. */
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
ImagingHorizontalBoxBlur(imTransposed, imTransposed, radius);
|
ImagingHorizontalBoxBlur(imTransposed, imTransposed, yradius);
|
||||||
}
|
}
|
||||||
/* Restore original orientation. */
|
/* Restore original orientation. */
|
||||||
ImagingTranspose(imOut, imTransposed);
|
ImagingTranspose(imOut, imTransposed);
|
||||||
|
@ -285,8 +285,8 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
|
||||||
return imOut;
|
return imOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
static float
|
||||||
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes) {
|
_gaussian_blur_radius(float radius, int passes) {
|
||||||
float sigma2, L, l, a;
|
float sigma2, L, l, a;
|
||||||
|
|
||||||
sigma2 = radius * radius / passes;
|
sigma2 = radius * radius / passes;
|
||||||
|
@ -299,5 +299,16 @@ ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes) {
|
||||||
a = (2 * l + 1) * (l * (l + 1) - 3 * sigma2);
|
a = (2 * l + 1) * (l * (l + 1) - 3 * sigma2);
|
||||||
a /= 6 * (sigma2 - (l + 1) * (l + 1));
|
a /= 6 * (sigma2 - (l + 1) * (l + 1));
|
||||||
|
|
||||||
return ImagingBoxBlur(imOut, imIn, l + a, passes);
|
return l + a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Imaging
|
||||||
|
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) {
|
||||||
|
return ImagingBoxBlur(
|
||||||
|
imOut,
|
||||||
|
imIn,
|
||||||
|
_gaussian_blur_radius(xradius, passes),
|
||||||
|
_gaussian_blur_radius(yradius, passes),
|
||||||
|
passes
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,7 +309,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingFlipTopBottom(Imaging imOut, Imaging imIn);
|
ImagingFlipTopBottom(Imaging imOut, Imaging imIn);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes);
|
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingGetBand(Imaging im, int band);
|
ImagingGetBand(Imaging im, int band);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
|
@ -376,7 +376,7 @@ ImagingTransform(
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold);
|
ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n);
|
ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingColorLUT3D_linear(
|
ImagingColorLUT3D_linear(
|
||||||
Imaging imOut,
|
Imaging imOut,
|
||||||
|
|
|
@ -36,7 +36,7 @@ ImagingUnsharpMask(
|
||||||
|
|
||||||
/* First, do a gaussian blur on the image, putting results in imOut
|
/* First, do a gaussian blur on the image, putting results in imOut
|
||||||
temporarily. All format checks are in gaussian blur. */
|
temporarily. All format checks are in gaussian blur. */
|
||||||
result = ImagingGaussianBlur(imOut, imIn, radius, 3);
|
result = ImagingGaussianBlur(imOut, imIn, radius, radius, 3);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user