Skip to content

Commit

Permalink
Merge pull request #7336 from radarhere/blur
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Aug 25, 2023
2 parents 643a52a + 9f54a11 commit c68bf7d
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 42 deletions.
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
18 changes: 15 additions & 3 deletions Tests/test_image_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
ImageFilter.MinFilter,
ImageFilter.ModeFilter,
ImageFilter.GaussianBlur,
ImageFilter.GaussianBlur(0),
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 +188,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)
30 changes: 23 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,12 @@ 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)
if xy == (0, 0):
return image.copy()
return image.gaussian_blur(xy)


class BoxBlur(MultibandFilter):
Expand All @@ -176,21 +182,31 @@ 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)
if xy == (0, 0):
return image.copy()
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
65 changes: 43 additions & 22 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 All @@ -258,35 +258,45 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) {
return ImagingError_ModeError();
}

imTransposed = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
if (!imTransposed) {
return NULL;
}

/* Apply blur in one dimension.
Use imOut as a destination at first pass,
then use imOut as a source too. */
ImagingHorizontalBoxBlur(imOut, imIn, radius);
for (i = 1; i < n; i++) {
ImagingHorizontalBoxBlur(imOut, imOut, radius);
}
/* 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);
if (xradius != 0) {
ImagingHorizontalBoxBlur(imOut, imIn, xradius);
for (i = 1; i < n; i++) {
ImagingHorizontalBoxBlur(imOut, imOut, xradius);
}
}
/* Restore original orientation. */
ImagingTranspose(imOut, imTransposed);
if (yradius != 0) {
imTransposed = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize);
if (!imTransposed) {
return NULL;
}

/* Transpose result for blur in another direction. */
ImagingTranspose(imTransposed, xradius == 0 ? imIn : imOut);

ImagingDelete(imTransposed);
/* Reuse imTransposed as a source and destination there. */
for (i = 0; i < n; i++) {
ImagingHorizontalBoxBlur(imTransposed, imTransposed, yradius);
}
/* Restore original orientation. */
ImagingTranspose(imOut, imTransposed);

ImagingDelete(imTransposed);
}
if (xradius == 0 && yradius == 0) {
if (!ImagingCopy2(imOut, imIn)) {
return NULL;
}
}

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 +309,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

0 comments on commit c68bf7d

Please sign in to comment.