-
-
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 19 commits
62ed526
637bc99
0807c85
e15b398
3494fcb
c0511fa
032183e
9bde470
fa37192
8856b03
2663478
52d7d5e
168c0bf
74266a5
8883018
0e2b57a
d241df1
20b3ccb
2b946f6
9060a85
73277d7
c6572cf
a66d730
dacf2f8
3394dc8
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 | ||
radarhere marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| :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): | ||
radarhere marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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( | ||
|
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. Name this just |
||
| 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 | ||
radarhere marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| :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. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.