Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added three new channel operations #4230

Merged
merged 11 commits into from Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 51 additions & 8 deletions Tests/test_imagechops.py
Expand Up @@ -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)

Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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 = []
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/ImageChops.rst
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions src/PIL/ImageChops.py
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions src/_imaging.c
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/libImaging/Chops.c
Expand Up @@ -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);
}
3 changes: 3 additions & 0 deletions src/libImaging/Imaging.h
Expand Up @@ -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);
Expand Down