mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Merge pull request #7336 from radarhere/blur
This commit is contained in:
commit
c68bf7df8f
|
@ -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):
|
||||
|
|
|
@ -23,9 +23,12 @@ from .helper import assert_image_equal, hopper
|
|||
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),
|
||||
),
|
||||
|
@ -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)
|
||||
|
|
|
@ -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,12 @@ 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)
|
||||
if xy == (0, 0):
|
||||
return image.copy()
|
||||
return image.gaussian_blur(xy)
|
||||
|
||||
|
||||
class BoxBlur(MultibandFilter):
|
||||
|
@ -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):
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
ImagingDelete(imTransposed);
|
||||
/* Transpose result for blur in another direction. */
|
||||
ImagingTranspose(imTransposed, xradius == 0 ? imIn : imOut);
|
||||
|
||||
/* 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;
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user