Blend Overlaps¶

In the blends chapter this notebook generates the venn diagram charts for various blend modes. You can plug in your own colors to generate your own data.

In [1]:
import json

import brushcue
from pathlib import Path

painter_dimensions = brushcue.vector2f_constant(256, 256)
stroke_width = 2
working_color_profile = brushcue.color_profile_s_r_g_b_linear()
output_color_profile = brushcue.color_profile_s_r_g_b()
ctx = brushcue.Context()

HOME = "/home/dito/dev/monorepo/writing/graphics/chapters/blend/assets"

def make_image(foreground: brushcue.Graph, background: brushcue.Graph, blend_fn) -> brushcue.Graph:

    center_1 = brushcue.point2f_from_components(82, 0)
    center_2 = brushcue.point2f_from_components(0, 0)

    def make_blend_circles() -> brushcue.Graph:
        painter_1 = brushcue.painter_new(working_color_profile)
        painter_2 = brushcue.painter_new(working_color_profile)
        render_style_1 = brushcue.render_style_fill_only(
            brushcue.fill_solid(background)
        )
        render_style_2 = brushcue.render_style_fill_only(
            brushcue.fill_solid(foreground)
        )
        instances = brushcue.transform2_to_list(brushcue.transform2_identity())
        painter_1 = brushcue.painter_add_ellipse_with_render_style(painter_1, center_1, painter_dimensions, 0, render_style_1, instances)
        painter_2 = brushcue.painter_add_ellipse_with_render_style(painter_2, center_2, painter_dimensions, 0, render_style_2, instances)
        composition_1 = brushcue.composition_painter(painter_1)
        composition_2 = brushcue.composition_painter(painter_2)
        return blend_fn(composition_2,composition_1, brushcue.transform2_identity())

    def make_circle_outlines() -> brushcue.Graph:
        brush = brushcue.brush_solid(
            brushcue.r_g_b_a_color_constant(0, 0, 0, 1),
            stroke_width
        )
        painter_1 = brushcue.painter_new(working_color_profile)
        painter_2 = brushcue.painter_new(working_color_profile)
        render_style = brushcue.render_style_brush_only(brush)
        instances = brushcue.transform2_to_list(brushcue.transform2_identity())
        painter_1 = brushcue.painter_add_ellipse_with_render_style(painter_1, center_1, painter_dimensions, 0, render_style, instances)
        painter_2 = brushcue.painter_add_ellipse_with_render_style(painter_2, center_2, painter_dimensions, 0, render_style, instances)
        composition_1 = brushcue.composition_painter(painter_1)
        composition_2 = brushcue.composition_painter(painter_2)
        return brushcue.composition_blend_alpha(composition_1, composition_2, brushcue.transform2_identity())

    blend_circles = make_blend_circles()
    outlines = make_circle_outlines()
    blended = brushcue.composition_blend_alpha(outlines, blend_circles, brushcue.transform2_identity())
    return brushcue.composition_color_convert(blended, output_color_profile)
In [2]:
colors = [
    brushcue.r_g_b_a_color_constant(1, 1, 1, 1),
    brushcue.r_g_b_a_color_constant(0, 0, 0, 1),
    brushcue.r_g_b_a_color_constant(1, 0, 0, 1),
    brushcue.r_g_b_a_color_constant(0, 1, 1, 1),
    brushcue.r_g_b_a_color_constant(0.6, 0.9, 0.4, 1),
    brushcue.r_g_b_a_color_constant(0.1, 0.2, 0.4, 1)
]

def clip_channel(value):
    return min(max(value, 0), 1)

def format_channel(value):
    return f"{clip_channel(value):.2f}".rstrip("0").rstrip(".")

def format_channels(color):
    return [format_channel(color[0]), format_channel(color[1]), format_channel(color[2])]

def math_add(c1, c2):
    return format_channels((c1[0] + c2[0], c1[1] + c2[1], c1[2] + c2[2]))

def math_subtract(c1, c2):
    return format_channels((c1[0] - c2[0], c1[1] - c2[1], c1[2] - c2[2]))

def math_multiply(c1, c2):
    return format_channels((c1[0] * c2[0], c1[1] * c2[1], c1[2] * c2[2]))

def math_max(c1, c2):
    return format_channels((max(c1[0], c2[0]), max(c1[1], c2[1]), max(c1[2], c2[2])))

def math_min(c1, c2):
    return format_channels((min(c1[0], c2[0]), min(c1[1], c2[1]), min(c1[2], c2[2])))

blend_fns = [
    ("add", brushcue.composition_blend_add, math_add),
    ("subtract", brushcue.composition_blend_subtract, math_subtract),
    ("multiply", brushcue.composition_blend_multiply, math_multiply),
    ("max", brushcue.composition_blend_max, math_max),
    ("min", brushcue.composition_blend_min, math_min),
]
In [ ]:
# Generates the Venn Diagrams

for (foreground_idx, foreground) in enumerate(colors):
    for (background_idx, background) in enumerate(colors):
        for (name, blend_fn, math_fn) in blend_fns:
            composition = make_image(
                foreground,
                background,
                blend_fn
            )
            result = composition.execute(ctx)
            output_bytes = result.as_composition().to_image_bytes(ctx)
            path = Path(f"{HOME}/{name}/{foreground_idx}/{background_idx}.png")
            foreground_color = foreground.execute(ctx).as_rgba_color()
            background_color = background.execute(ctx).as_rgba_color()
            computation = {
                "foreground": format_channels(foreground_color),
                "background": format_channels(background_color),
                "result": math_fn(foreground_color, background_color),
            }
            path.parent.mkdir(parents=True, exist_ok=True)
            with open(path, "wb") as f:
                f.write(bytes(output_bytes))
            with open(f"{HOME}/{name}/{foreground_idx}/{background_idx}.json", "w") as f:
                json.dump(computation, f)
new pipeline: painter_Elliptical_fill_solid_no_brush_sample_true
new pipeline: blend_Add_rgba16float_compute_bg_false_fg_false
new pipeline: painter_Elliptical_no_fill_brush_solid_sample_true
new pipeline: blend_Alpha_rgba16float_compute_bg_false_fg_false
new pipeline: color_transformer_shader_rgba16float_compute_13509697929820575703_inarr_false
new pipeline: blend_Subtract_rgba16float_compute_bg_false_fg_false
new pipeline: blend_Multiply_rgba16float_compute_bg_false_fg_false
new pipeline: blend_Max_rgba16float_compute_bg_false_fg_false
new pipeline: blend_Min_rgba16float_compute_bg_false_fg_false
In [ ]:
# Generates the Individual Swatches

for (idx, color) in enumerate(colors):
    painter = brushcue.painter_new(working_color_profile)
    render_style = brushcue.render_style_brush_and_fill(
        brushcue.brush_solid(brushcue.r_g_b_a_color_constant(0, 0, 0, 1), stroke_width),
        brushcue.fill_solid(color)
    )
    instances = brushcue.transform2_to_list(brushcue.transform2_identity())
    painter = brushcue.painter_add_ellipse_with_render_style(painter, brushcue.point2f_from_components(0, 0), painter_dimensions, 0, render_style, instances)
    composition = brushcue.composition_painter(painter)
    composition = brushcue.composition_color_convert(composition, output_color_profile)
    result = composition.execute(ctx)
    output_bytes = result.as_composition().to_image_bytes(ctx)
    path = Path(f"{HOME}/swatches/color_{idx}.png")
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "wb") as f:
        f.write(bytes(output_bytes))
    rgba = color.execute(ctx).as_rgba_color()
    color_json = {
        "color": format_channels(rgba)
    }
    with open(f"{HOME}/swatches/color_{idx}.json", "w") as f:
        json.dump(color_json, f)
In [ ]: