-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Neon and sepia filters #9341
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
base: main
Are you sure you want to change the base?
Neon and sepia filters #9341
Changes from all commits
62ed526
637bc99
0807c85
e15b398
3494fcb
c0511fa
032183e
9bde470
fa37192
8856b03
2663478
52d7d5e
168c0bf
74266a5
8883018
0e2b57a
d241df1
20b3ccb
2b946f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,7 +24,7 @@ | |||||||||
| from collections.abc import Sequence | ||||||||||
| from typing import Literal, Protocol, cast, overload | ||||||||||
|
|
||||||||||
| from . import ExifTags, Image, ImagePalette | ||||||||||
| from . import ExifTags, Image, ImageFilter, ImagePalette | ||||||||||
|
|
||||||||||
| # | ||||||||||
| # helpers | ||||||||||
|
|
@@ -623,6 +623,100 @@ def grayscale(image: Image.Image) -> Image.Image: | |||||||||
| return image.convert("L") | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def sepia(image: Image.Image) -> Image.Image: | ||||||||||
| """ | ||||||||||
| Apply a sepia tone effect to an image. | ||||||||||
|
|
||||||||||
| :param image: The image to modify. | ||||||||||
| :return: An image. | ||||||||||
|
|
||||||||||
| """ | ||||||||||
| if image.mode != "RGB": | ||||||||||
| image = image.convert("RGB") | ||||||||||
|
|
||||||||||
| out = Image.new("RGB", image.size) | ||||||||||
|
|
||||||||||
| for x in range(image.width): | ||||||||||
| for y in range(image.height): | ||||||||||
| value = image.getpixel((x, y)) | ||||||||||
| assert isinstance(value, tuple) | ||||||||||
| r, g, b = value | ||||||||||
|
|
||||||||||
| tr = 0.393 * r + 0.769 * g + 0.189 * b | ||||||||||
| tg = 0.349 * r + 0.686 * g + 0.168 * b | ||||||||||
| tb = 0.272 * r + 0.534 * g + 0.131 * b | ||||||||||
|
|
||||||||||
| out.putpixel((x, y), tuple(min(255, int(c)) for c in (tr, tg, tb))) | ||||||||||
|
|
||||||||||
| return out | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def sobel(image: Image.Image) -> Image.Image: | ||||||||||
| """ | ||||||||||
| Applies a Sobel edge-detection filter to the given image. | ||||||||||
|
|
||||||||||
| This function computes the Sobel gradient magnitude using the | ||||||||||
| horizontal (Gx) and vertical (Gy) Sobel kernels. | ||||||||||
|
|
||||||||||
| :param image: the image to apply the filter | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| :return: An image. | ||||||||||
| """ | ||||||||||
| if image.mode != "L": | ||||||||||
| image = image.convert("L") | ||||||||||
|
|
||||||||||
| Kx = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]] | ||||||||||
| Ky = [[1, 2, 1], [0, 0, 0], [-1, -2, -1]] | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just want to confirm - you're sure the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had used an inverted kernel for the Sobel operator in the y direction. The right one is the |
||||||||||
|
|
||||||||||
| out = Image.new("L", image.size) | ||||||||||
|
|
||||||||||
| for y in range(1, image.height - 1): | ||||||||||
| for x in range(1, image.width - 1): | ||||||||||
|
Comment on lines
+672
to
+673
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Nitpick: I think it would be nice if the order of the loops matched the order in |
||||||||||
|
|
||||||||||
| gx = gy = 0.0 | ||||||||||
|
|
||||||||||
| for dy in (-1, 0, 1): | ||||||||||
| for dx in (-1, 0, 1): | ||||||||||
| v = image.getpixel((x + dx, y + dy)) | ||||||||||
| assert isinstance(v, (int, float)) | ||||||||||
|
|
||||||||||
| gx += v * Kx[dy + 1][dx + 1] | ||||||||||
| gy += v * Ky[dy + 1][dx + 1] | ||||||||||
|
|
||||||||||
| # Approximate gradient magnitude and clamp to [0, 255] | ||||||||||
| mag = int(min(255, abs(gx) + abs(gy))) | ||||||||||
| out.putpixel((x, y), mag) | ||||||||||
|
|
||||||||||
| return out | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def neon_effect( | ||||||||||
| image: Image.Image, color: tuple[int, int, int] = (255, 0, 255), alpha: float = 0.2 | ||||||||||
| ) -> Image.Image: | ||||||||||
| """ | ||||||||||
| Apply a neon glow effect to an image using edge detection, | ||||||||||
| blur-based glow generation, colorization, and alpha blending. | ||||||||||
| It calls all auxiliary functions required to generate | ||||||||||
| the final result. | ||||||||||
|
|
||||||||||
| :param image: Image to create the effect | ||||||||||
| :param color: RGB color used for neon effect | ||||||||||
| :alpha: controls the intensity of the neon effect | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| :return: An image | ||||||||||
| """ | ||||||||||
| edges = sobel(image).filter(ImageFilter.GaussianBlur(2)) | ||||||||||
|
|
||||||||||
| # Apply a glow-enhancing mask transformation | ||||||||||
| glow = edges.point(lambda value: 255 - ((255 - value) ** 2 // 255)) | ||||||||||
|
|
||||||||||
| # Apply a color tint to the intensity mask | ||||||||||
| neon = Image.merge( | ||||||||||
| "RGB", | ||||||||||
| tuple(glow.point(lambda value: min(255, int(value * c / 255))) for c in color), | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| return Image.blend(image, neon, alpha) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def invert(image: Image.Image) -> Image.Image: | ||||||||||
| """ | ||||||||||
| Invert (negate) the image. | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests
sepia()with a non-RGB image.