Skip to content

Commit

Permalink
Merge 0ff4b53 into ce09d40
Browse files Browse the repository at this point in the history
  • Loading branch information
homm committed Oct 15, 2014
2 parents ce09d40 + 0ff4b53 commit a7ad04f
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 2 deletions.
17 changes: 17 additions & 0 deletions PIL/ImageOps.py
Expand Up @@ -441,3 +441,20 @@ def unsharp_mask(im, radius=None, percent=None, threshold=None):
return im.im.unsharp_mask(radius, percent, threshold)

usm = unsharp_mask


def box_blur(image, radius):
"""
Apply box blur to given image. Box blur is operation where
each pixel becomes the average value of pixels in given radius.
Supports float radius and very large ones. Fast implementation,
works in linear time relative to the radius.
:param image: The image to blur.
:param radius: Size of the box in one direction. Radius 0 does not blur,
radius 1 takes 1 pixel in all directions, i.e. 9 pixels in total.
:return: An image.
"""
image.load()

return image._new(image.im.box_blur(radius))
180 changes: 180 additions & 0 deletions Tests/test_box_blur.py
@@ -0,0 +1,180 @@
from helper import unittest, PillowTestCase

from PIL import Image, ImageOps


sample = Image.new("L", (7, 5))
sample.putdata(sum([
[210, 50, 20, 10, 220, 230, 80],
[190, 210, 20, 180, 170, 40, 110],
[120, 210, 250, 60, 220, 0, 220],
[220, 40, 230, 80, 130, 250, 40],
[250, 0, 80, 30, 60, 20, 110],
], []))


class TestBoxBlurApi(PillowTestCase):

def test_imageops(self):
i = ImageOps.box_blur(sample, 1)
self.assertEqual(i.mode, sample.mode)
self.assertEqual(i.size, sample.size)
self.assertIsInstance(i, Image.Image)


class TestBoxBlur(PillowTestCase):

def box_blur(self, image, radius=1):
return image._new(image.im.box_blur(radius))

def assertImage(self, im, data, delta=0):
it = iter(im.getdata())
for data_row in data:
im_row = [next(it) for _ in range(im.size[0])]
if any(
abs(data_v - im_v) > delta
for data_v, im_v in zip(data_row, im_row)
):
self.assertEqual(im_row, data_row)
self.assertRaises(StopIteration, next, it)

def test_color_modes(self):
self.assertRaises(ValueError, self.box_blur, sample.convert("1"))
self.assertRaises(ValueError, self.box_blur, sample.convert("P"))
self.box_blur(sample.convert("L"))
self.box_blur(sample.convert("LA"))
self.assertRaises(ValueError, self.box_blur, sample.convert("I"))
self.assertRaises(ValueError, self.box_blur, sample.convert("F"))
self.box_blur(sample.convert("RGB"))
self.box_blur(sample.convert("RGBA"))
self.box_blur(sample.convert("CMYK"))
self.assertRaises(ValueError, self.box_blur, sample.convert("YCbCr"))

def test_radius_0(self):
self.assertImage(
self.box_blur(sample, 0),
[
[210, 50, 20, 10, 220, 230, 80],
[190, 210, 20, 180, 170, 40, 110],
[120, 210, 250, 60, 220, 0, 220],
[220, 40, 230, 80, 130, 250, 40],
[250, 0, 80, 30, 60, 20, 110],
]
)

def test_radius_0_02(self):
self.assertImage(
self.box_blur(sample, 0.02),
[
[206, 55, 20, 17, 215, 223, 83],
[189, 203, 31, 171, 169, 46, 110],
[125, 206, 241, 69, 210, 13, 210],
[215, 49, 221, 82, 131, 235, 48],
[244, 7, 80, 32, 60, 27, 107],
],
delta=2,
)

def test_radius_0_05(self):
self.assertImage(
self.box_blur(sample, 0.05),
[
[202, 62, 22, 27, 209, 215, 88],
[188, 194, 44, 161, 168, 56, 111],
[131, 201, 229, 81, 198, 31, 198],
[209, 62, 209, 86, 133, 216, 59],
[237, 17, 80, 36, 60, 35, 103],
],
delta=2,
)

def test_radius_0_1(self):
self.assertImage(
self.box_blur(sample, 0.1),
[
[196, 72, 24, 40, 200, 203, 93],
[187, 183, 62, 148, 166, 68, 111],
[139, 193, 213, 96, 182, 54, 182],
[201, 78, 193, 91, 133, 191, 73],
[227, 31, 80, 42, 61, 47, 99],
],
delta=1,
)

def test_radius_0_5(self):
self.assertImage(
self.box_blur(sample, 0.5),
[
[176, 101, 46, 83, 163, 165, 111],
[176, 149, 108, 122, 144, 120, 117],
[164, 171, 159, 141, 134, 119, 129],
[170, 136, 133, 114, 116, 124, 109],
[184, 95, 72, 70, 69, 81, 89],
],
delta=1,
)

def test_radius_1(self):
self.assertImage(
self.box_blur(sample, 1),
[
[170, 109, 63, 97, 146, 153, 116],
[168, 142, 112, 128, 126, 143, 121],
[169, 166, 142, 149, 126, 131, 114],
[159, 156, 109, 127, 94, 117, 112],
[164, 128, 63, 87, 76, 89, 90],
],
delta=1,
)

def test_radius_1_5(self):
self.assertImage(
self.box_blur(sample, 1.5),
[
[155, 120, 105, 112, 124, 137, 130],
[160, 136, 124, 125, 127, 134, 130],
[166, 147, 130, 125, 120, 121, 119],
[168, 145, 119, 109, 103, 105, 110],
[168, 134, 96, 85, 85, 89, 97],
],
delta=1,
)

def test_radius_bigger_then_half(self):
self.assertImage(
self.box_blur(sample, 3),
[
[144, 145, 142, 128, 114, 115, 117],
[148, 145, 137, 122, 109, 111, 112],
[152, 145, 131, 117, 103, 107, 108],
[156, 144, 126, 111, 97, 102, 103],
[160, 144, 121, 106, 92, 98, 99],
],
delta=1,
)

def test_radius_bigger_then_width(self):
self.assertImage(
self.box_blur(sample, 10),
[
[158, 153, 147, 141, 135, 129, 123],
[159, 153, 147, 141, 136, 130, 124],
[159, 154, 148, 142, 136, 130, 124],
[160, 154, 148, 142, 137, 131, 125],
[160, 155, 149, 143, 137, 131, 125],
],
delta=0,
)

def test_exteme_large_radius(self):
self.assertImage(
self.box_blur(sample, 600),
[
[162, 162, 162, 162, 162, 162, 162],
[162, 162, 162, 162, 162, 162, 162],
[162, 162, 162, 162, 162, 162, 162],
[162, 162, 162, 162, 162, 162, 162],
[162, 162, 162, 162, 162, 162, 162],
],
delta=1,
)
24 changes: 23 additions & 1 deletion _imaging.c
Expand Up @@ -1791,7 +1791,6 @@ _unsharp_mask(ImagingObject* self, PyObject* args)
if (!PyArg_ParseTuple(args, "fii", &radius, &percent, &threshold))
return NULL;


imIn = self->image;
imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
if (!imOut)
Expand All @@ -1804,6 +1803,27 @@ _unsharp_mask(ImagingObject* self, PyObject* args)
}
#endif

static PyObject*
_box_blur(ImagingObject* self, PyObject* args)
{
Imaging imIn;
Imaging imOut;

float radius;
if (!PyArg_ParseTuple(args, "f", &radius))
return NULL;

imIn = self->image;
imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
if (!imOut)
return NULL;

if (!ImagingBoxBlur(imIn, imOut, radius))
return NULL;

return PyImagingNew(imOut);
}

/* -------------------------------------------------------------------- */

static PyObject*
Expand Down Expand Up @@ -3078,6 +3098,8 @@ static struct PyMethodDef methods[] = {
{"unsharp_mask", (PyCFunction)_unsharp_mask, 1},
#endif

{"box_blur", (PyCFunction)_box_blur, 1},

#ifdef WITH_EFFECTS
/* Special effects */
{"effect_spread", (PyCFunction)_effect_spread, 1},
Expand Down

0 comments on commit a7ad04f

Please sign in to comment.