From 5e5dfbad81152d8a5cfbcb556a1d9e58a86f8bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dag=20W=C3=A4stberg?= Date: Fri, 22 Nov 2019 14:03:59 +0100 Subject: [PATCH] add hardlight and softlight chops --- Tests/test_imagechops.py | 49 ++++++++++++++++++++++++++++++++-------- src/PIL/ImageChops.py | 21 +++++++++++++++++ src/_imaging.c | 24 ++++++++++++++++++++ src/libImaging/Chops.c | 18 +++++++++++++++ src/libImaging/Imaging.h | 2 ++ 5 files changed, 104 insertions(+), 10 deletions(-) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 8dec6a1d5..82e469cc1 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -38,6 +38,9 @@ def test_sanity(): ImageChops.blend(im, im, 0.5) ImageChops.composite(im, im, im) + ImageChops.softlight(im, im) + ImageChops.hardlight(im, im) + ImageChops.offset(im, 10) ImageChops.offset(im, 10, 20) @@ -319,9 +322,9 @@ def test_subtract_scale_offset(): # Act new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) - # Assert - assert new.getbbox() == (0, 0, 100, 100) - assert new.getpixel((50, 50)) == (100, 202, 100) + # Assert + assert new.getbbox() == (0, 0, 100, 100) + assert new.getpixel((50, 50)) == (100, 202, 100) def test_subtract_clip(): @@ -332,8 +335,8 @@ def test_subtract_clip(): # Act new = ImageChops.subtract(im1, im2) - # Assert - assert new.getpixel((50, 50)) == (0, 0, 127) + # Assert + assert new.getpixel((50, 50)) == (0, 0, 127) def test_subtract_modulo(): @@ -344,10 +347,10 @@ def test_subtract_modulo(): # Act new = ImageChops.subtract_modulo(im1, im2) - # Assert - assert new.getbbox() == (25, 50, 76, 76) - assert new.getpixel((50, 50)) == GREEN - assert new.getpixel((50, 51)) == BLACK + # Assert + assert new.getbbox() == (25, 50, 76, 76) + assert new.getpixel((50, 50)) == GREEN + assert new.getpixel((50, 51)) == BLACK def test_subtract_modulo_no_clip(): @@ -358,8 +361,34 @@ def test_subtract_modulo_no_clip(): # Act new = ImageChops.subtract_modulo(im1, im2) + # Assert + assert new.getpixel((50, 50)) == (241, 167, 127) + + +def test_softlight(self): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.softlight(im1, im2) + # Assert - assert new.getpixel((50, 50)) == (241, 167, 127) + self.assertEqual(new.getpixel((64, 64)), (163, 54, 32)) + self.assertEqual(new.getpixel((15, 100)), (1, 1, 3)) + + +def test_hardlight(self): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.hardlight(im1, im2) + + # Assert + self.assertEqual(new.getpixel((64, 64)), (144, 50, 27)) + self.assertEqual(new.getpixel((15, 100)), (1, 1, 2)) def test_logical(): diff --git a/src/PIL/ImageChops.py b/src/PIL/ImageChops.py index b1f71b5e7..d33186fb2 100644 --- a/src/PIL/ImageChops.py +++ b/src/PIL/ImageChops.py @@ -138,6 +138,27 @@ def screen(image1, image2): image2.load() return image1._new(image1.im.chop_screen(image2.im)) +def softlight(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_softlight(image2.im)) + +def hardlight(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_hardlight(image2.im)) def add(image1, image2, scale=1.0, offset=0): """ diff --git a/src/_imaging.c b/src/_imaging.c index 190b312bc..c146a975f 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2406,6 +2406,27 @@ _chop_subtract_modulo(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingChopSubtractModulo(self->image, imagep->image)); } +static PyObject* +_chop_softlight(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_hardlight(ImagingObject* self, PyObject* args) +{ + ImagingObject* imagep; + + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + return NULL; + + return PyImagingNew(ImagingChopHardLight(self->image, imagep->image)); +} #endif @@ -3325,6 +3346,9 @@ static struct PyMethodDef methods[] = { {"chop_and", (PyCFunction)_chop_and, 1}, {"chop_or", (PyCFunction)_chop_or, 1}, {"chop_xor", (PyCFunction)_chop_xor, 1}, + {"chop_softlight", (PyCFunction)_chop_softlight, 1}, + {"chop_hardlight", (PyCFunction)_chop_hardlight, 1}, + #endif #ifdef WITH_UNSHARPMASK diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index 8059b6ffb..302a8c2b9 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -146,3 +146,21 @@ 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[1]) * (255 - in2[x]) / 255 ) )) / 255), NULL ); + 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); +} \ No newline at end of file diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 71dc9c003..99ce6d6d6 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -339,6 +339,8 @@ 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); /* "1" images only */ extern Imaging ImagingChopAnd(Imaging imIn1, Imaging imIn2);