552 lines
13 KiB
Cython
552 lines
13 KiB
Cython
#cython: cdivision=True
|
|
#cython: boundscheck=False
|
|
#cython: nonecheck=False
|
|
#cython: wraparound=False
|
|
|
|
"""Color Mixer
|
|
|
|
NumPy does not do overflow checking when adding or multiplying
|
|
integers, so currently the only way to clip results efficiently
|
|
(without making copies of the data) is with an extension such as this
|
|
one.
|
|
|
|
"""
|
|
|
|
cimport numpy as cnp
|
|
from libc.math cimport exp, pow
|
|
cnp.import_array()
|
|
|
|
|
|
def add(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
Py_ssize_t channel, Py_ssize_t amount):
|
|
"""Add a given amount to a color channel of `stateimg`, and
|
|
store the result in `img`. Overflow is clipped.
|
|
|
|
Parameters
|
|
----------
|
|
img : (M, N, 3) ndarray of uint8
|
|
Output image.
|
|
stateimg : (M, N, 3) ndarray of uint8
|
|
Input image.
|
|
channel : int
|
|
Channel (0 for "red", 1 for "green", 2 for "blue").
|
|
amount : int
|
|
Value to add.
|
|
|
|
"""
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
cdef Py_ssize_t k = channel
|
|
cdef Py_ssize_t n = amount
|
|
|
|
cdef cnp.int16_t op_result
|
|
|
|
cdef cnp.uint8_t lut[256]
|
|
|
|
cdef Py_ssize_t i, j, l
|
|
|
|
with nogil:
|
|
|
|
for l from 0 <= l < 256:
|
|
op_result = <cnp.int16_t>(l + n)
|
|
if op_result > 255:
|
|
op_result = 255
|
|
elif op_result < 0:
|
|
op_result = 0
|
|
else:
|
|
pass
|
|
lut[l] = <cnp.uint8_t>op_result
|
|
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
img[i, j, k] = lut[stateimg[i,j,k]]
|
|
|
|
|
|
def multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
Py_ssize_t channel, float amount):
|
|
"""Multiply a color channel of `stateimg` by a certain amount, and
|
|
store the result in `img`. Overflow is clipped.
|
|
|
|
Parameters
|
|
----------
|
|
img : (M, N, 3) ndarray of uint8
|
|
Output image.
|
|
stateimg : (M, N, 3) ndarray of uint8
|
|
Input image.
|
|
channel : int
|
|
Channel (0 for "red", 1 for "green", 2 for "blue").
|
|
amount : float
|
|
Multiplication factor.
|
|
|
|
"""
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
cdef Py_ssize_t k = channel
|
|
cdef float n = amount
|
|
|
|
cdef float op_result
|
|
|
|
cdef cnp.uint8_t lut[256]
|
|
|
|
cdef Py_ssize_t i, j, l
|
|
|
|
with nogil:
|
|
|
|
for l from 0 <= l < 256:
|
|
op_result = l * n
|
|
if op_result > 255:
|
|
op_result = 255
|
|
elif op_result < 0:
|
|
op_result = 0
|
|
else:
|
|
pass
|
|
lut[l] = <cnp.uint8_t>op_result
|
|
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
img[i,j,k] = lut[stateimg[i,j,k]]
|
|
|
|
|
|
def brightness(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
float factor, Py_ssize_t offset):
|
|
"""Modify the brightness of an image.
|
|
'factor' is multiplied to all channels, which are
|
|
then added by 'amount'. Overflow is clipped.
|
|
|
|
Parameters
|
|
----------
|
|
img : (M, N, 3) ndarray of uint8
|
|
Output image.
|
|
stateimg : (M, N, 3) ndarray of uint8
|
|
Input image.
|
|
factor : float
|
|
Multiplication factor.
|
|
offset : int
|
|
Ammount to add to each channel.
|
|
|
|
"""
|
|
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
|
|
cdef float op_result
|
|
cdef cnp.uint8_t lut[256]
|
|
|
|
cdef Py_ssize_t i, j, k
|
|
with nogil:
|
|
|
|
for k from 0 <= k < 256:
|
|
op_result = k * factor + offset
|
|
if op_result > 255:
|
|
op_result = 255
|
|
elif op_result < 0:
|
|
op_result = 0
|
|
else:
|
|
pass
|
|
lut[k] = <cnp.uint8_t>op_result
|
|
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
img[i,j,0] = lut[stateimg[i,j,0]]
|
|
img[i,j,1] = lut[stateimg[i,j,1]]
|
|
img[i,j,2] = lut[stateimg[i,j,2]]
|
|
|
|
|
|
def sigmoid_gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
float alpha, float beta):
|
|
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
|
|
cdef Py_ssize_t i, j, k
|
|
|
|
cdef float c1 = 1 / (1 + exp(beta))
|
|
cdef float c2 = 1 / (1 + exp(beta - alpha)) - c1
|
|
|
|
cdef cnp.uint8_t lut[256]
|
|
|
|
with nogil:
|
|
|
|
# compute the lut
|
|
for k from 0 <= k < 256:
|
|
lut[k] = <cnp.uint8_t>(((1 / (1 + exp(beta - (k / 255.) * alpha)))
|
|
- c1) * 255 / c2)
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
img[i,j,0] = lut[stateimg[i,j,0]]
|
|
img[i,j,1] = lut[stateimg[i,j,1]]
|
|
img[i,j,2] = lut[stateimg[i,j,2]]
|
|
|
|
|
|
def gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
float gamma):
|
|
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
|
|
cdef cnp.uint8_t lut[256]
|
|
|
|
cdef Py_ssize_t i, j, k
|
|
|
|
if gamma == 0:
|
|
gamma = 0.00000000000000000001
|
|
gamma = 1./gamma
|
|
|
|
with nogil:
|
|
|
|
# compute the lut
|
|
for k from 0 <= k < 256:
|
|
lut[k] = <cnp.uint8_t>((pow((k / 255.), gamma) * 255))
|
|
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
img[i,j,0] = lut[stateimg[i,j,0]]
|
|
img[i,j,1] = lut[stateimg[i,j,1]]
|
|
img[i,j,2] = lut[stateimg[i,j,2]]
|
|
|
|
|
|
cdef void rgb_2_hsv(float* RGB, float* HSV) nogil:
|
|
cdef float R, G, B, H, S, V, MAX, MIN
|
|
R = RGB[0]
|
|
G = RGB[1]
|
|
B = RGB[2]
|
|
|
|
if R > 255:
|
|
R = 255
|
|
elif R < 0:
|
|
R = 0
|
|
else:
|
|
pass
|
|
|
|
if G > 255:
|
|
G = 255
|
|
elif G < 0:
|
|
G = 0
|
|
else:
|
|
pass
|
|
|
|
if B > 255:
|
|
B = 255
|
|
elif B < 0:
|
|
B = 0
|
|
else:
|
|
pass
|
|
|
|
|
|
if R < G:
|
|
MIN = R
|
|
MAX = G
|
|
else:
|
|
MIN = G
|
|
MAX = R
|
|
|
|
if B < MIN:
|
|
MIN = B
|
|
elif B > MAX:
|
|
MAX = B
|
|
else:
|
|
pass
|
|
|
|
V = MAX / 255.
|
|
|
|
if MAX == MIN:
|
|
H = 0.
|
|
elif MAX == R:
|
|
H = (60 * (G - B) / (MAX - MIN) + 360) % 360
|
|
elif MAX == G:
|
|
H = 60 * (B - R) / (MAX - MIN) + 120
|
|
else:
|
|
H = 60 * (R - G) / (MAX - MIN) + 240
|
|
|
|
if MAX == 0:
|
|
S = 0
|
|
else:
|
|
S = 1 - MIN / MAX
|
|
|
|
HSV[0] = H
|
|
HSV[1] = S
|
|
HSV[2] = V
|
|
|
|
|
|
cdef void hsv_2_rgb(float* HSV, float* RGB) nogil:
|
|
cdef float H, S, V
|
|
cdef float f, p, q, t, r, g, b
|
|
cdef Py_ssize_t hi
|
|
|
|
H = HSV[0]
|
|
S = HSV[1]
|
|
V = HSV[2]
|
|
|
|
if H > 360:
|
|
H = H % 360
|
|
elif H < 0:
|
|
H = 360 - ((-1 * H) % 360)
|
|
else:
|
|
pass
|
|
|
|
if S > 1:
|
|
S = 1
|
|
elif S < 0:
|
|
S = 0
|
|
else:
|
|
pass
|
|
|
|
if V > 1:
|
|
V = 1
|
|
elif V < 0:
|
|
V = 0
|
|
else:
|
|
pass
|
|
|
|
hi = (<int>(H / 60.)) % 6
|
|
f = (H / 60.) - (<int>(H / 60.))
|
|
p = V * (1 - S)
|
|
q = V * (1 - f * S)
|
|
t = V * (1 - (1 -f) * S)
|
|
|
|
if hi == 0:
|
|
r = V
|
|
g = t
|
|
b = p
|
|
elif hi == 1:
|
|
r = q
|
|
g = V
|
|
b = p
|
|
elif hi == 2:
|
|
r = p
|
|
g = V
|
|
b = t
|
|
elif hi == 3:
|
|
r = p
|
|
g = q
|
|
b = V
|
|
elif hi == 4:
|
|
r = t
|
|
g = p
|
|
b = V
|
|
else:
|
|
r = V
|
|
g = p
|
|
b = q
|
|
|
|
RGB[0] = r
|
|
RGB[1] = g
|
|
RGB[2] = b
|
|
|
|
|
|
def py_hsv_2_rgb(H, S, V):
|
|
'''Convert an HSV value to RGB.
|
|
|
|
Automatic clipping.
|
|
|
|
Parameters
|
|
----------
|
|
H : float
|
|
From 0. - 360.
|
|
S : float
|
|
From 0. - 1.
|
|
V : float
|
|
From 0. - 1.
|
|
|
|
Returns
|
|
-------
|
|
out : (R, G, B) ints
|
|
Each from 0 - 255
|
|
|
|
conversion convention from here:
|
|
https://en.wikipedia.org/wiki/HSL_and_HSV
|
|
|
|
'''
|
|
cdef float HSV[3]
|
|
cdef float RGB[3]
|
|
|
|
HSV[0] = H
|
|
HSV[1] = S
|
|
HSV[2] = V
|
|
|
|
hsv_2_rgb(HSV, RGB)
|
|
|
|
R = int(RGB[0] * 255)
|
|
G = int(RGB[1] * 255)
|
|
B = int(RGB[2] * 255)
|
|
|
|
return (R, G, B)
|
|
|
|
|
|
def py_rgb_2_hsv(R, G, B):
|
|
'''Convert an HSV value to RGB.
|
|
|
|
Automatic clipping.
|
|
|
|
Parameters
|
|
----------
|
|
R : int
|
|
From 0. - 255.
|
|
G : int
|
|
From 0. - 255.
|
|
B : int
|
|
From 0. - 255.
|
|
|
|
Returns
|
|
-------
|
|
out : (H, S, V) floats
|
|
Ranges (0...360), (0...1), (0...1)
|
|
|
|
conversion convention from here:
|
|
https://en.wikipedia.org/wiki/HSL_and_HSV
|
|
|
|
'''
|
|
cdef float HSV[3]
|
|
cdef float RGB[3]
|
|
|
|
RGB[0] = R
|
|
RGB[1] = G
|
|
RGB[2] = B
|
|
|
|
rgb_2_hsv(RGB, HSV)
|
|
|
|
H = HSV[0]
|
|
S = HSV[1]
|
|
V = HSV[2]
|
|
|
|
return (H, S, V)
|
|
|
|
|
|
def hsv_add(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
float h_amt, float s_amt, float v_amt):
|
|
"""Modify the image color by specifying additive HSV Values.
|
|
|
|
Since the underlying images are RGB, all three values HSV
|
|
must be specified at the same time.
|
|
|
|
The RGB triplet in the image is converted to HSV, the operation
|
|
is applied, and then the HSV triplet is converted back to RGB
|
|
|
|
HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.)
|
|
then the operation is performed and any overflow is clipped, then the
|
|
reverse transform is performed. Those are the ranges to keep in mind,
|
|
when passing in values.
|
|
|
|
Parameters
|
|
----------
|
|
img : (M, N, 3) ndarray of uint8
|
|
Output image.
|
|
stateimg : (M, N, 3) ndarray of uint8
|
|
Input image.
|
|
h_amt : float
|
|
Ammount to add to H channel.
|
|
s_amt : float
|
|
Ammount to add to S channel.
|
|
v_amt : float
|
|
Ammount to add to V channel.
|
|
|
|
|
|
"""
|
|
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
|
|
cdef float HSV[3]
|
|
cdef float RGB[3]
|
|
|
|
cdef Py_ssize_t i, j
|
|
|
|
with nogil:
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
RGB[0] = stateimg[i, j, 0]
|
|
RGB[1] = stateimg[i, j, 1]
|
|
RGB[2] = stateimg[i, j, 2]
|
|
|
|
rgb_2_hsv(RGB, HSV)
|
|
|
|
# Add operation
|
|
HSV[0] += h_amt
|
|
HSV[1] += s_amt
|
|
HSV[2] += v_amt
|
|
|
|
hsv_2_rgb(HSV, RGB)
|
|
|
|
RGB[0] *= 255
|
|
RGB[1] *= 255
|
|
RGB[2] *= 255
|
|
|
|
img[i, j, 0] = <cnp.uint8_t>RGB[0]
|
|
img[i, j, 1] = <cnp.uint8_t>RGB[1]
|
|
img[i, j, 2] = <cnp.uint8_t>RGB[2]
|
|
|
|
|
|
def hsv_multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img,
|
|
cnp.ndarray[cnp.uint8_t, ndim=3] stateimg,
|
|
float h_amt, float s_amt, float v_amt):
|
|
"""Modify the image color by specifying multiplicative HSV Values.
|
|
|
|
Since the underlying images are RGB, all three values HSV
|
|
must be specified at the same time.
|
|
|
|
The RGB triplet in the image is converted to HSV, the operation
|
|
is applied, and then the HSV triplet is converted back to RGB
|
|
|
|
HSV values are scaled to H(0. - 360.), S(0. - 1.), V(0. - 1.)
|
|
then the operation is performed and any overflow is clipped, then the
|
|
reverse transform is performed. Those are the ranges to keep in mind,
|
|
when passing in values.
|
|
|
|
Note that since hue is in degrees, it makes no sense to multiply
|
|
that channel, thus an add operation is performed on the hue. And the
|
|
values given for h_amt, should be the same as for hsv_add
|
|
|
|
Parameters
|
|
----------
|
|
img : (M, N, 3) ndarray of uint8
|
|
Output image.
|
|
stateimg : (M, N, 3) ndarray of uint8
|
|
Input image.
|
|
h_amt : float
|
|
Ammount to add to H channel.
|
|
s_amt : float
|
|
Ammount by which to multiply S channel.
|
|
v_amt : float
|
|
Ammount by which to multiply V channel.
|
|
|
|
|
|
"""
|
|
|
|
cdef Py_ssize_t height = img.shape[0]
|
|
cdef Py_ssize_t width = img.shape[1]
|
|
|
|
cdef float HSV[3]
|
|
cdef float RGB[3]
|
|
|
|
cdef Py_ssize_t i, j
|
|
|
|
with nogil:
|
|
for i from 0 <= i < height:
|
|
for j from 0 <= j < width:
|
|
RGB[0] = stateimg[i, j, 0]
|
|
RGB[1] = stateimg[i, j, 1]
|
|
RGB[2] = stateimg[i, j, 2]
|
|
|
|
rgb_2_hsv(RGB, HSV)
|
|
|
|
# Multiply operation
|
|
HSV[0] += h_amt
|
|
HSV[1] *= s_amt
|
|
HSV[2] *= v_amt
|
|
|
|
hsv_2_rgb(HSV, RGB)
|
|
|
|
RGB[0] *= 255
|
|
RGB[1] *= 255
|
|
RGB[2] *= 255
|
|
|
|
img[i, j, 0] = <cnp.uint8_t>RGB[0]
|
|
img[i, j, 1] = <cnp.uint8_t>RGB[1]
|
|
img[i, j, 2] = <cnp.uint8_t>RGB[2]
|