1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
from .types import 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
# go through 8 adjacent pixels
for x in range(x0, x2 + 1):
for y in range(y0, y2 + 1):
if x == x1 and y == y1:
continue
# brightness delta between the center pixel and adjacent one
delta = color_delta(img, img, pos, (y * width + x) * 4, True)
# count the number of equal, darker and brighter adjacent pixels
if delta == 0:
zeroes += 1
# if found more than 2 equal siblings, it's definitely not anti-aliasing
if zeroes > 2:
return False
# remember the darkest pixel
elif delta < min_delta:
min_delta = delta
min_x = x
min_y = y
# remember the brightest pixel
elif delta > max_delta:
max_delta = delta
max_x = x
max_y = y
# if there are no both darker and brighter pixels among siblings, it's not anti-aliasing
if min_delta == 0 or max_delta == 0:
return False
# if either the darkest or the brightest pixel has 3+ equal siblings in both images
# (definitely not anti-aliased), this pixel is anti-aliased
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)
# go through 8 adjacent pixels
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:
# brightness difference 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)
|