def hue_rotate(im, deg=0): """Applies hue rotation. A hue rotate operation is equivalent to the following matrix operation: | R' | | a00 a01 a02 0 0 | | R | | G' | | a10 a11 a12 0 0 | | G | | B' | = | a20 a21 a22 0 0 | * | B | | A' | | 0 0 0 1 0 | | A | | 1 | | 0 0 0 0 1 | | 1 | where | a00 a01 a02 | [+0.213 +0.715 +0.072] | a10 a11 a12 | = [+0.213 +0.715 +0.072] + | a20 a21 a22 | [+0.213 +0.715 +0.072] [+0.787 -0.715 -0.072] cos(hueRotate value) * [-0.213 +0.285 -0.072] + [-0.213 -0.715 +0.928] [-0.213 -0.715+0.928] sin(hueRotate value) * [+0.143 +0.140-0.283] [-0.787 +0.715+0.072] See the W3C document: https://www.w3.org/TR/SVG11/filters.html#feColorMatrixValuesAttribute Arguments: im: An input image. amount: An optional integer/float. The hue rotate value (degrees). Defaults to 0. Returns: The output image. """ cos_hue = math.cos(deg * math.pi / 180) sin_hue = math.sin(deg * math.pi / 180) matrix = [ .213 + cos_hue * .787 - sin_hue * .213, .715 - cos_hue * .715 - sin_hue * .715, .072 - cos_hue * .072 + sin_hue * .928, 0, .213 - cos_hue * .213 + sin_hue * .143, .715 + cos_hue * .285 + sin_hue * .140, .072 - cos_hue * .072 - sin_hue * .283, 0, .213 - cos_hue * .213 - sin_hue * .787, .715 - cos_hue * .715 + sin_hue * .715, .072 + cos_hue * .928 + sin_hue * .072, 0, ] rotated = util.or_convert(im, 'RGB').convert('RGB', matrix) return util.or_convert(rotated, im.mode)
def brooklyn(im): """Applies Brooklyn filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [168, 223, 193, .4]) cm1 = css.blending.overlay(cb, cs1) cs2 = util.fill(cb.size, [196, 183, 200]) cm2 = css.blending.overlay(cb, cs2) gradient_mask = util.radial_gradient_mask(cb.size, length=.7) cr = Image.composite(cm1, cm2, gradient_mask) cr = css.contrast(cr, .9) cr = css.brightness(cr, 1.1) return cr
def nashville(im): """Applies Nashville filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [247, 176, 153]) cm1 = ImageChops.darker(cb, cs1) cm1 = Image.blend(cb, cm1, .56) cs2 = util.fill(cb.size, [0, 70, 150]) cm2 = ImageChops.lighter(cm1, cs2) cr = Image.blend(cm1, cm2, .4) cr = css.sepia(cr, .2) cr = css.contrast(cr, 1.2) cr = css.brightness(cr, 1.05) cr = css.saturate(cr, 1.2) return cr
def mayfair(im): """Applies Mayfair filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') size = cb.size pos = (.4, .4) cs1 = util.fill(size, [255, 255, 255, .8]) cm1 = css.blending.overlay(cb, cs1) cs2 = util.fill(size, [255, 200, 200, .6]) cm2 = css.blending.overlay(cb, cs2) cs3 = util.fill(size, [17, 17, 17]) cm3 = css.blending.overlay(cb, cs3) mask1 = util.radial_gradient_mask(size, scale=.3, center=pos) cs = Image.composite(cm1, cm2, mask1) mask2 = util.radial_gradient_mask(size, length=.3, scale=.6, center=pos) cs = Image.composite(cs, cm3, mask2) cr = Image.blend(cb, cs, .4) # opacity cr = css.contrast(cr, 1.1) cr = css.saturate(cr, 1.1) return cr
def xpro2(im): """Applies X-pro II filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [230, 231, 224]) cs2 = util.fill(cb.size, [43, 42, 161]) cs2 = Image.blend(cb, cs2, .6) gradient_mask = util.radial_gradient_mask(cb.size, length=.4, scale=1.1) cs = Image.composite(cs1, cs2, gradient_mask) # TODO: improve alpha blending cm1 = css.blending.color_burn(cb, cs) cm2 = cm1.copy() cm2 = Image.blend(cb, cm2, .6) cr = Image.composite(cm1, cm2, gradient_mask) cr = css.sepia(cr, .3) return cr
def test_or_convert_different_mode(): w, h = (4, 4) im = util.fill((w, h), [0, 127, 255, .5]) # RGBA converted = util.or_convert(im, 'RGB') assert converted != im assert converted.mode == 'RGB'
def rise(im): """Applies Rise filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [236, 205, 169, .15]) cm1 = css.blending.multiply(cb, cs1) cs2 = util.fill(cb.size, [50, 30, 7, .4]) cm2 = css.blending.multiply(cb, cs2) gradient_mask1 = util.radial_gradient_mask(cb.size, length=.55) cm = Image.composite(cm1, cm2, gradient_mask1) cs3 = util.fill(cb.size, [232, 197, 152, .8]) cm3 = css.blending.overlay(cm, cs3) gradient_mask2 = util.radial_gradient_mask(cb.size, scale=.9) cm_ = Image.composite(cm3, cm, gradient_mask2) cr = Image.blend(cm, cm_, .6) # opacity cr = css.brightness(cr, 1.05) cr = css.sepia(cr, .2) cr = css.contrast(cr, .9) cr = css.saturate(cr, .9) return cr
def test_or_convert_same_mode(): w, h = (4, 4) im = util.fill((w, h), [0, 127, 255]) # RGB converted = util.or_convert(im, 'RGB') assert converted == im # should be the same instance assert converted.mode == 'RGB'
def grayscale(im, amount=1): """Converts image to grayscale. A grayscale operation is equivalent to the following matrix operation: | R' | |0.2126+0.7874g 0.7152-0.7152g 0.0722-0.0722g 0 0 | | R | | G' | |0.2126-0.2126g 0.7152+0.2848g 0.0722-0.0722g 0 0 | | G | | B' | = |0.2126-0.2126g 0.7152-0.7152g 0.0722+0.9278g 0 0 | * | B | | A' | | 0 0 0 1 0 | | A | | 1 | | 0 0 0 0 1 | | 1 | See the W3C document: https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent Arguments: im: An input image. amount: An optional integer/float. The filter amount (percentage). Defaults to 1. Returns: The output image. Raises: AssertionError: if `amount` is less than 0. """ assert amount >= 0 g = 1 - min(amount, 1) matrix = [ .2126 + .7874 * g, .7152 - .7152 * g, .0722 - .0722 * g, 0, .2126 - .2126 * g, .7152 + .2848 * g, .0722 - .0722 * g, 0, .2126 - .2126 * g, .7152 - .7152 * g, .0722 + .9278 * g, 0, ] grayscaled = util.or_convert(im, 'RGB').convert('RGB', matrix) return util.or_convert(grayscaled, im.mode)
def saturate(im, amount=1): """Saturates image. A saturate operation is equivalent to the following matrix operation: | R' | |0.213+0.787s 0.715-0.715s 0.072-0.072s 0 0 | | R | | G' | |0.213-0.213s 0.715+0.285s 0.072-0.072s 0 0 | | G | | B' | = |0.213-0.213s 0.715-0.715s 0.072+0.928s 0 0 | * | B | | A' | | 0 0 0 1 0 | | A | | 1 | | 0 0 0 0 1 | | 1 | See the W3C document: https://www.w3.org/TR/SVG11/filters.html#feColorMatrixValuesAttribute Arguments: im: An input image. amount: An optional integer/float. The filter amount (percentage). Defaults to 1. Returns: The output image. Raises: AssertionError: if `amount` is less than 0. """ assert amount >= 0 matrix = [ .213 + .787 * amount, .715 - .715 * amount, .072 - .072 * amount, 0, .213 - .213 * amount, .715 + .285 * amount, .072 - .072 * amount, 0, .213 - .213 * amount, .715 - .715 * amount, .072 + .928 * amount, 0, ] saturated = util.or_convert(im, 'RGB').convert('RGB', matrix) return util.or_convert(saturated, im.mode)
def sepia(im, amount=1): """Converts image to sepia. A sepia operation is equivalent to the following matrix operation: | R' | |0.393+0.607s 0.769-0.769s 0.189-0.189s 0 0 | | R | | G' | |0.349-0.349s 0.686+0.314s 0.168-0.168s 0 0 | | G | | B' | = |0.272-0.272g 0.534-0.534g 0.131+0.869g 0 0 | * | B | | A' | | 0 0 0 1 0 | | A | | 1 | | 0 0 0 0 1 | | 1 | See the W3C document: https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent Arguments: im: An input image. amount: An optional integer/float. The filter amount (percentage). Defaults to 1. Returns: The output image. Raises: AssertionError: if `amount` is less than 0. """ assert amount >= 0 amount = 1 - min(amount, 1) matrix = [ .393 + .607 * amount, .769 - .769 * amount, .189 - .189 * amount, 0, .349 - .349 * amount, .686 + .314 * amount, .168 - .168 * amount, 0, .272 - .272 * amount, .534 - .534 * amount, .131 + .869 * amount, 0, ] sepia_toned = util.or_convert(im, 'RGB').convert('RGB', matrix) return util.or_convert(sepia_toned, im.mode)
def perpetua(im): """Applies Perpetua filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.linear_gradient(cb.size, [0, 91, 154], [230, 193, 61], False) cs = css.blending.soft_light(cb, cs) cr = Image.blend(cb, cs, .5) # opacity return cr
def inkwell(im): """Applies Inkwell filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cr = css.sepia(cb, .3) cr = css.contrast(cr, 1.1) cr = css.brightness(cr, 1.1) cr = css.grayscale(cr) return cr
def brannan(im): """Applies Brannan filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [161, 44, 199, .31]) cr = css.blending.lighten(cb, cs) cr = css.sepia(cr, .5) cr = css.contrast(cr, 1.4) return cr
def toaster(im): """Applies Toaster filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.radial_gradient(cb.size, [(128, 78, 15), (59, 0, 59)]) cr = css.blending.screen(cb, cs) cr = css.contrast(cr, 1.5) cr = css.brightness(cr, .9) return cr
def kelvin(im): """Applies Kelvin filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [56, 44, 52]) cs = css.blending.color_dodge(cb, cs1) cs2 = util.fill(cb.size, [183, 125, 33]) cr = css.blending.overlay(cs, cs2) return cr
def gingham(im): """Applies Gingham filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [230, 230, 250]) cr = css.blending.soft_light(cb, cs) cr = css.brightness(cr, 1.05) cr = css.hue_rotate(cr, -10) return cr
def clarendon(im): """Applies Clarendon filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [127, 187, 227, .2]) cr = css.blending.overlay(cb, cs) cr = css.contrast(cr, 1.2) cr = css.saturate(cr, 1.35) return cr
def earlybird(im): """Applies Earlybird filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.radial_gradient(cb.size, [(208, 186, 142), (54, 3, 9), (29, 2, 16)], [.2, .85, 1]) cr = css.blending.overlay(cb, cs) cr = css.contrast(cr, .9) cr = css.sepia(cr, .2) return cr
def _1977(im): """Applies 1977 filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [243, 106, 188, .3]) cr = css.blending.screen(cb, cs) cr = css.contrast(cr, 1.1) cr = css.brightness(cr, 1.1) cr = css.saturate(cr, 1.3) return cr
def lark(im): """Applies Lark filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [34, 37, 63]) cm1 = css.blending.color_dodge(cb, cs1) cs2 = util.fill(cb.size, [242, 242, 242]) cm2 = ImageChops.darker(cm1, cs2) cr = Image.blend(cm1, cm2, .8) return cr
def stinson(im): """Applies Stinson filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [240, 149, 128, .2]) cr = css.blending.soft_light(cb, cs) cr = css.contrast(cr, .75) cr = css.saturate(cr, .85) cr = css.brightness(cr, 1.15) return cr
def valencia(im): """Applies Valencia filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [58, 3, 57]) cs = css.blending.exclusion(cb, cs) cr = Image.blend(cb, cs, .5) cr = css.contrast(cr, 1.08) cr = css.brightness(cr, 1.08) cr = css.sepia(cr, .08) return cr
def maven(im): """Applies Maven filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [3, 230, 26, .2]) cr = css.blending.hue(cb, cs) cr = css.sepia(cr, .25) cr = css.brightness(cr, .95) cr = css.contrast(cr, .95) cr = css.saturate(cr, 1.5) return cr
def lofi(im): """Applies Lo-Fi filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [34, 34, 34]) cs = css.blending.multiply(cb, cs) mask = util.radial_gradient_mask(cb.size, length=.7, scale=1.5) cr = Image.composite(cb, cs, mask) cr = css.saturate(cr, 1.1) cr = css.contrast(cr, 1.5) return cr
def slumber(im): """Applies Slumber filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [69, 41, 12, .4]) cm = css.blending.lighten(cb, cs1) cs2 = util.fill(cb.size, [125, 105, 24, .5]) cr = css.blending.soft_light(cm, cs2) cr = css.saturate(cr, .66) cr = css.brightness(cr, 1.05) return cr
def hudson(im): """Applies Hudson filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.radial_gradient(cb.size, [(166, 177, 255), (52, 33, 52)], [.5, 1]) cs = css.blending.multiply(cb, cs) cr = Image.blend(cb, cs, .5) # opacity cr = css.brightness(cr, 1.2) cr = css.contrast(cr, .9) cr = css.saturate(cr, 1.1) return cr
def walden(im): """Applies Walden filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [0, 68, 204]) cs = ImageChops.screen(cb, cs) cr = Image.blend(cb, cs, .3) cr = css.brightness(cr, 1.1) cr = css.hue_rotate(cr, -10) cr = css.sepia(cr, .3) cr = css.saturate(cr, 1.6) return cr
def reyes(im): """Applies Reyes filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs = util.fill(cb.size, [239, 205, 173]) cs = css.blending.soft_light(cb, cs) cr = Image.blend(cb, cs, .5) cr = css.sepia(cr, .22) cr = css.brightness(cr, 1.1) cr = css.contrast(cr, .85) cr = css.saturate(cr, .75) return cr
def moon(im): """Applies Moon filter. Arguments: im: An input image. Returns: The output image. """ cb = util.or_convert(im, 'RGB') cs1 = util.fill(cb.size, [160, 160, 160]) cs = css.blending.soft_light(cb, cs1) cs2 = util.fill(cb.size, [56, 56, 56]) cr = css.blending.lighten(cs, cs2) cr = css.grayscale(cr) cr = css.contrast(cr, 1.1) cr = css.brightness(cr, 1.1) return cr