The secret to great looking Native Ads Colors Colours Kolors

A number of people have recently complimented us on how the Sidelines’ native ad unit looks great no matter where it is displayed and no matter what the content is. Having a seamless look and feel is what allows us to insert full-fledged 1000+ word pieces of sponsored content on thousands of pages daily. The cool part is that everything involving the look & feel of our unit is determined algorithmically, from fonts and font sizes to shades and accent colors, all in under 5ms.

The basic premise is to understand the environment our native ad unit is placed in real-time and make intelligent decisions. It would be nice to assume that the website operator has already put a lot of thought into the look and feel of their site and just inherit their styles. More often than not however, we need to make changes on the fly, usually to enhance readability or blend in brand colors and accents. These changes have to be subtle, for we want our unit to be truly native to its host and not look like a boxed banner unit. Today, I wanted to share a few useful functions I use to help with color selection.

For these functions, I use the excellent Grapefruit library for Python. The first function is based on this formula that gives us the perceived brightness of a color.

def is_light_color(color):
    """
    :param color: a grapefruit color object
    :return: true if the color is consider light/bright, false if dark
    """
    r, g, b = color.rgb
    if (r * 0.299 + g * 0.587 + b * 0.114) > 0.5:
        return True
    else:
        return False

The second function helps us determine if there is enough contrast between two colors and is based on this W3C standard

def _nonlinear_to_linear_rgb(r,g,b):
    """
    Helper function for conversion from nonlinear to linear intensities
    :return: adjusted r,g,b values
    """
    return [c / 12.92 if c <= 0.03928 else ((c+0.055)/1.055)**2.4 for c in (r,g,b)]

def is_enough_contrast(fg_color, bg_color, text_size=16):
    """
    Check to see if there is enough contrast between those two colors, see
    for the formula

    :param bg_color: the background color as a grapefruit object
    :param fg_color: the foreground oclor as a grapefruit object
    :param text_size: the text size in pixels

    :return: True if it at least satisfies AA
    """
    bg_r, bg_g, bg_b = _nonlinear_to_linear_rgb(*bg_color.rgb)
    fg_r, fg_g, fg_b = _nonlinear_to_linear_rgb(*fg_color.rgb)

    l1 = 0.2126 * bg_r + 0.7152 * bg_g + 0.0722 * bg_b
    l2 = 0.2126 * fg_r + 0.7152 * fg_g + 0.0722 * fg_b
    if l1 > l2:
        ratio = (l1+0.05)/(l2+0.05)
    else:
        ratio = (l2+0.05)/(l1+0.05)

    if text_size >= 18:
        return ratio >= 3
    else:
        return ratio >= 4.5

How would one use these functions? There are many possibilities, some pseudocode included below. Obviously there are a ton of edge cases we need to account for which I omit here for brevity.

fg_color, bg_color = get_environment_original_colors()

if is_light_color(bg_color):
    # assume light theme
    while not is_enough_contrast(fg_color, bg_color, paragraph_font_size):
       fg_color = fg_color.darken(0.05)
else:
    # assume dark theme
    while not is_enough_contrast(fg_color, bg_color, paragraph_font_size):
       fg_color = fg_color.lighten(0.05)

Beyond the obvious example above, we use the above functions to influence decisions on color accents, highlights, shadows, shades, panels and a lot more. In a future post, I’ll talk about typography and how we programmatically choose fonts and font sizes that are responsive across different screen sizes and blend natively with the host page while maintaining a pleasant reading experience.