Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii #7336

Merged
merged 2 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Tests/test_box_blur.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_imageops_box_blur():


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):
Expand Down
17 changes: 14 additions & 3 deletions Tests/test_image_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
ImageFilter.ModeFilter,
ImageFilter.GaussianBlur,
ImageFilter.GaussianBlur(5),
ImageFilter.GaussianBlur((2, 5)),
ImageFilter.BoxBlur(0),
ImageFilter.BoxBlur(5),
ImageFilter.BoxBlur((2, 5)),
ImageFilter.UnsharpMask,
ImageFilter.UnsharpMask(10),
),
Expand Down Expand Up @@ -185,12 +187,21 @@ def test_consistency_5x5(mode):
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):
ImageFilter.BoxBlur(-2)
ImageFilter.BoxBlur(radius)

im = hopper()
box_blur_filter = ImageFilter.BoxBlur(2)
box_blur_filter.radius = -2
box_blur_filter.radius = radius
with pytest.raises(ValueError):
im.filter(box_blur_filter)
26 changes: 19 additions & 7 deletions src/PIL/ImageFilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ class GaussianBlur(MultibandFilter):
approximates a Gaussian kernel. For details on accuracy see
<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"
Expand All @@ -166,7 +167,10 @@ def __init__(self, radius=2):
self.radius = radius

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):
Expand All @@ -176,21 +180,29 @@ class BoxBlur(MultibandFilter):
which runs in linear time relative to the size of the image
for any radius value.

:param radius: Size of the box in one direction. Radius 0 does not blur,
returns an identical image. Radius 1 takes 1 pixel
in each direction, i.e. 9 pixels in total.
:param radius: Size of the box in a direction. Either a sequence of two numbers for
x and y, or a single number for both.

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"

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"
raise ValueError(msg)
self.radius = radius

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):
Expand Down
12 changes: 6 additions & 6 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -1075,9 +1075,9 @@ _gaussian_blur(ImagingObject *self, PyObject *args) {
Imaging imIn;
Imaging imOut;

float radius = 0;
float xradius, yradius;
int passes = 3;
if (!PyArg_ParseTuple(args, "f|i", &radius, &passes)) {
if (!PyArg_ParseTuple(args, "(ff)|i", &xradius, &yradius, &passes)) {
return NULL;
}

Expand All @@ -1087,7 +1087,7 @@ _gaussian_blur(ImagingObject *self, PyObject *args) {
return NULL;
}

if (!ImagingGaussianBlur(imOut, imIn, radius, passes)) {
if (!ImagingGaussianBlur(imOut, imIn, xradius, yradius, passes)) {
ImagingDelete(imOut);
return NULL;
}
Expand Down Expand Up @@ -2131,9 +2131,9 @@ _box_blur(ImagingObject *self, PyObject *args) {
Imaging imIn;
Imaging imOut;

float radius;
float xradius, yradius;
int n = 1;
if (!PyArg_ParseTuple(args, "f|i", &radius, &n)) {
if (!PyArg_ParseTuple(args, "(ff)|i", &xradius, &yradius, &n)) {
return NULL;
}

Expand All @@ -2143,7 +2143,7 @@ _box_blur(ImagingObject *self, PyObject *args) {
return NULL;
}

if (!ImagingBoxBlur(imOut, imIn, radius, n)) {
if (!ImagingBoxBlur(imOut, imIn, xradius, yradius, n)) {
ImagingDelete(imOut);
return NULL;
}
Expand Down
27 changes: 19 additions & 8 deletions src/libImaging/BoxBlur.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,14 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
}

Imaging
ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) {
int i;
Imaging imTransposed;

if (n < 1) {
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");
}

Expand Down Expand Up @@ -266,16 +266,16 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
/* Apply blur in one dimension.
Use imOut as a destination at first pass,
then use imOut as a source too. */
ImagingHorizontalBoxBlur(imOut, imIn, radius);
ImagingHorizontalBoxBlur(imOut, imIn, xradius);
for (i = 1; i < n; i++) {
ImagingHorizontalBoxBlur(imOut, imOut, radius);
ImagingHorizontalBoxBlur(imOut, imOut, xradius);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could skip passes if it’s radius == 0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I've pushed a commit

}
/* Transpose result for blur in another direction. */
ImagingTranspose(imTransposed, imOut);

/* Reuse imTransposed as a source and destination there. */
for (i = 0; i < n; i++) {
ImagingHorizontalBoxBlur(imTransposed, imTransposed, radius);
ImagingHorizontalBoxBlur(imTransposed, imTransposed, yradius);
}
/* Restore original orientation. */
ImagingTranspose(imOut, imTransposed);
Expand All @@ -285,8 +285,8 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
return imOut;
}

Imaging
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes) {
static float
_gaussian_blur_radius(float radius, int passes) {
float sigma2, L, l, a;

sigma2 = radius * radius / passes;
Expand All @@ -299,5 +299,16 @@ ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes) {
a = (2 * l + 1) * (l * (l + 1) - 3 * sigma2);
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
);
}
4 changes: 2 additions & 2 deletions src/libImaging/Imaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn);
extern Imaging
ImagingFlipTopBottom(Imaging imOut, Imaging imIn);
extern Imaging
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes);
ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes);
extern Imaging
ImagingGetBand(Imaging im, int band);
extern Imaging
Expand Down Expand Up @@ -376,7 +376,7 @@ ImagingTransform(
extern Imaging
ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold);
extern Imaging
ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n);
ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n);
extern Imaging
ImagingColorLUT3D_linear(
Imaging imOut,
Expand Down
2 changes: 1 addition & 1 deletion src/libImaging/UnsharpMask.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ ImagingUnsharpMask(

/* First, do a gaussian blur on the image, putting results in imOut
temporarily. All format checks are in gaussian blur. */
result = ImagingGaussianBlur(imOut, imIn, radius, 3);
result = ImagingGaussianBlur(imOut, imIn, radius, radius, 3);
if (!result) {
return NULL;
}
Expand Down
Loading