diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 8dec6a1d563..7d042cb9feb 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -38,6 +38,10 @@ def test_sanity(): ImageChops.blend(im, im, 0.5) ImageChops.composite(im, im, im) + ImageChops.soft_light(im, im) + ImageChops.hard_light(im, im) + ImageChops.overlay(im, im) + ImageChops.offset(im, 10) ImageChops.offset(im, 10, 20) @@ -209,8 +213,8 @@ def test_lighter_image(): # Act new = ImageChops.lighter(im1, im2) - # Assert - assert_image_equal(new, im1) + # Assert + assert_image_equal(new, im1) def test_lighter_pixel(): @@ -275,13 +279,13 @@ def test_offset(): # Act new = ImageChops.offset(im, xoffset, yoffset) - # Assert - assert new.getbbox() == (0, 45, 100, 96) - assert new.getpixel((50, 50)) == BLACK - assert new.getpixel((50 + xoffset, 50 + yoffset)) == DARK_GREEN + # Assert + assert new.getbbox() == (0, 45, 100, 96) + assert new.getpixel((50, 50)) == BLACK + assert new.getpixel((50 + xoffset, 50 + yoffset)) == DARK_GREEN - # Test no yoffset - assert ImageChops.offset(im, xoffset) == ImageChops.offset(im, xoffset, xoffset) + # Test no yoffset + assert ImageChops.offset(im, xoffset) == ImageChops.offset(im, xoffset, xoffset) def test_screen(): @@ -362,6 +366,45 @@ def test_subtract_modulo_no_clip(): assert new.getpixel((50, 50)) == (241, 167, 127) +def test_soft_light(): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.soft_light(im1, im2) + + # Assert + assert new.getpixel((64, 64)) == (163, 54, 32) + assert new.getpixel((15, 100)) == (1, 1, 3) + + +def test_hard_light(): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.hard_light(im1, im2) + + # Assert + assert new.getpixel((64, 64)) == (144, 50, 27) + assert new.getpixel((15, 100)) == (1, 1, 2) + + +def test_overlay(): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.overlay(im1, im2) + + # Assert + assert new.getpixel((64, 64)) == (159, 50, 27) + assert new.getpixel((15, 100)) == (1, 1, 2) + + def test_logical(): def table(op, a, b): out = [] diff --git a/docs/reference/ImageChops.rst b/docs/reference/ImageChops.rst index 6c8f11253ac..fb742254903 100644 --- a/docs/reference/ImageChops.rst +++ b/docs/reference/ImageChops.rst @@ -36,6 +36,9 @@ operations in this module). .. autofunction:: PIL.ImageChops.logical_or .. autofunction:: PIL.ImageChops.logical_xor .. autofunction:: PIL.ImageChops.multiply +.. autofunction:: PIL.ImageChops.soft_light +.. autofunction:: PIL.ImageChops.hard_light +.. autofunction:: PIL.ImageChops.overlay .. py:method:: PIL.ImageChops.offset(image, xoffset, yoffset=None) Returns a copy of the image where data has been offset by the given diff --git a/src/PIL/ImageChops.py b/src/PIL/ImageChops.py index b1f71b5e71e..2d13b529fef 100644 --- a/src/PIL/ImageChops.py +++ b/src/PIL/ImageChops.py @@ -139,6 +139,42 @@ def screen(image1, image2): return image1._new(image1.im.chop_screen(image2.im)) +def soft_light(image1, image2): + """ + Superimposes two images on top of each other using the Soft Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_soft_light(image2.im)) + + +def hard_light(image1, image2): + """ + Superimposes two images on top of each other using the Hard Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_hard_light(image2.im)) + + +def overlay(image1, image2): + """ + Superimposes two images on top of each other using the Overlay algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_overlay(image2.im)) + + def add(image1, image2, scale=1.0, offset=0): """ Adds two images, dividing the result by scale and adding the diff --git a/src/_imaging.c b/src/_imaging.c index 190b312bc4b..f40b19e4dfa 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2406,6 +2406,38 @@ _chop_subtract_modulo(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopSubtractModulo(self->image, imagep->image)); } +static PyObject* +_chop_soft_light(ImagingObject* self, PyObject* args) +{ + ImagingObject* imagep; + + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + return NULL; + + return PyImagingNew(ImagingChopSoftLight(self->image, imagep->image)); +} + +static PyObject* +_chop_hard_light(ImagingObject* self, PyObject* args) +{ + ImagingObject* imagep; + + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + return NULL; + + return PyImagingNew(ImagingChopHardLight(self->image, imagep->image)); +} + +static PyObject* +_chop_overlay(ImagingObject* self, PyObject* args) +{ + ImagingObject* imagep; + + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + return NULL; + + return PyImagingNew(ImagingOverlay(self->image, imagep->image)); +} #endif @@ -3325,6 +3357,10 @@ static struct PyMethodDef methods[] = { {"chop_and", (PyCFunction)_chop_and, 1}, {"chop_or", (PyCFunction)_chop_or, 1}, {"chop_xor", (PyCFunction)_chop_xor, 1}, + {"chop_soft_light", (PyCFunction)_chop_soft_light, 1}, + {"chop_hard_light", (PyCFunction)_chop_hard_light, 1}, + {"chop_overlay", (PyCFunction)_chop_overlay, 1}, + #endif #ifdef WITH_UNSHARPMASK diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index 8059b6ffbd6..a1673dff6c5 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -146,3 +146,27 @@ ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) { CHOP2(in1[x] - in2[x], NULL); } + +Imaging +ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) +{ + CHOP2( (((255-in1[x]) * (in1[x]*in2[x]) ) / 65536) + + (in1[x] * ( 255 - ( (255 - in1[x]) * (255 - in2[x] ) / 255) )) / 255 + , NULL ); +} + +Imaging +ImagingChopHardLight(Imaging imIn1, Imaging imIn2) +{ + CHOP2( (in2[x]<128) ? ( (in1[x]*in2[x])/127) + : 255 - ( ((255-in2[x]) * (255-in1[x])) / 127) + , NULL); +} + +Imaging +ImagingOverlay(Imaging imIn1, Imaging imIn2) +{ + CHOP2( (in1[x]<128) ? ( (in1[x]*in2[x])/127) + : 255 - ( ((255-in1[x]) * (255-in2[x])) / 127) + , NULL); +} diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 71dc9c003f8..9032fcf0783 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -339,6 +339,9 @@ extern Imaging ImagingChopSubtract( Imaging imIn1, Imaging imIn2, float scale, int offset); extern Imaging ImagingChopAddModulo(Imaging imIn1, Imaging imIn2); extern Imaging ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2); +extern Imaging ImagingChopSoftLight(Imaging imIn1, Imaging imIn2); +extern Imaging ImagingChopHardLight(Imaging imIn1, Imaging imIn2); +extern Imaging ImagingOverlay(Imaging imIn1, Imaging imIn2); /* "1" images only */ extern Imaging ImagingChopAnd(Imaging imIn1, Imaging imIn2);