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

This commit is contained in:
Andrew Murray 2023-08-12 12:09:20 +10:00
parent 08b538780d
commit c167d7a269
7 changed files with 62 additions and 28 deletions

View File

@ -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):

View File

@ -24,8 +24,10 @@ from .helper import assert_image_equal, hopper
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),
),
@ -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)

View File

@ -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"
@ -166,7 +167,10 @@ class GaussianBlur(MultibandFilter):
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):
@ -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):

View File

@ -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;
}
@ -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;
}
@ -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;
}
@ -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;
}

View File

@ -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");
}
@ -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);
}
/* 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);
@ -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;
@ -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
);
}

View File

@ -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
@ -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,

View File

@ -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;
}