from collections.abc import Collection
from .types import ImageArea, ImageSequence, MutableImageSequence
def antialiased(img: ImageSequence, x1: int, y1: int, width: int, height: int, img2: ImageSequence) -> bool:
"""
check if a pixel is likely a part of anti-aliasing;
based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009
"""
x0 = max(x1 - 1, 0)
y0 = max(y1 - 1, 0)
x2 = min(x1 + 1, width - 1)
y2 = min(y1 + 1, height - 1)
pos = (y1 * width + x1) * 4
zeroes = int(x1 == x0 or x1 == x2 or y1 == y0 or y1 == y2)
min_delta = max_delta = 0.0
min_x = min_y = max_x = max_y = 0
for x in range(x0, x2 + 1):
for y in range(y0, y2 + 1):
if x == x1 and y == y1:
continue
delta = color_delta(img, img, pos, (y * width + x) * 4, y_only=True)
if delta == 0:
zeroes += 1
if zeroes > 2:
return False
elif delta < min_delta:
min_delta = delta
min_x = x
min_y = y
elif delta > max_delta:
max_delta = delta
max_x = x
max_y = y
if min_delta == 0 or max_delta == 0:
return False
return (
has_many_siblings(img, min_x, min_y, width, height) and has_many_siblings(img2, min_x, min_y, width, height)
) or (
has_many_siblings(img, max_x, max_y, width, height) and has_many_siblings(img2, max_x, max_y, width, height)
)
def has_many_siblings(img: ImageSequence, x1: int, y1: int, width: int, height: int) -> bool:
"""
check if a pixel has 3+ adjacent pixels of the same color.
"""
x0 = max(x1 - 1, 0)
y0 = max(y1 - 1, 0)
x2 = min(x1 + 1, width - 1)
y2 = min(y1 + 1, height - 1)
pos = (y1 * width + x1) * 4
zeroes = int(x1 == x0 or x1 == x2 or y1 == y0 or y1 == y2)
for x in range(x0, x2 + 1):
for y in range(y0, y2 + 1):
if x == x1 and y == y1:
continue
pos2 = (y * width + x) * 4
if all(img[pos + offset] == img[pos2 + offset] for offset in range(4)):
zeroes += 1
if zeroes > 2:
return True
return False
def color_delta(img1: ImageSequence, img2: ImageSequence, k: int, m: int, y_only: bool = False) -> float:
"""
calculate color difference according to the paper "Measuring perceived color difference
using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos
"""
r1, g1, b1, a1 = [img1[k + offset] for offset in range(4)]
r2, g2, b2, a2 = [img2[m + offset] for offset in range(4)]
if a1 == a2 and r1 == r2 and g1 == g2 and b1 == b2:
return 0.0
if a1 < 255:
a1 /= 255
r1, b1, g1 = blendRGB(r1, b1, g1, a1)
if a2 < 255:
a2 /= 255
r2, b2, g2 = blendRGB(r2, b2, g2, a2)
y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2)
if y_only:
return y
i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2)
q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2)
return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q
def rgb2y(r: float, g: float, b: float) -> float:
return r * 0.29889531 + g * 0.58662247 + b * 0.11448223
def rgb2i(r: float, g: float, b: float) -> float:
return r * 0.59597799 - g * 0.27417610 - b * 0.32180189
def rgb2q(r: float, g: float, b: float) -> float:
return r * 0.21147017 - g * 0.52261711 + b * 0.31114694
def blendRGB(r: float, g: float, b: float, a: float):
"""
Blend r, g, and b with a
:param r: red channel to blend with a
:param g: green channel to blend with a
:param b: blue channel to blend with a
:param a: alpha to blend with
:return: tuple of blended r, g, b
"""
return blend(r, a), blend(g, a), blend(b, a)
def blend(c: float, a: float) -> float:
"""blend semi-transparent color with white"""
return 255 + (c - 255) * a
def draw_pixel(output: MutableImageSequence, pos: int, r: float, g: float, b: float) -> None:
output[pos + 0] = int(r)
output[pos + 1] = int(g)
output[pos + 2] = int(b)
output[pos + 3] = 255
def draw_gray_pixel(img: ImageSequence, i: int, alpha: float, output: MutableImageSequence) -> None:
r = img[i + 0]
g = img[i + 1]
b = img[i + 2]
val = blend(rgb2y(r, g, b), alpha * img[i + 3] / 255)
draw_pixel(output, i, val, val, val)
def ignorable_coord(x: int, y: int, masked_areas: Collection[ImageArea]) -> bool:
for (x0, y0, x1, y1) in masked_areas:
if x >= x0 - 2 and x < x1 + 2 and y >= y0 - 2 and y < y1 + 2:
return True
return False