Blend Overlaps¶
In the blends chapter this notebook generates the venn diagram charts for various blend modes. The color channels in the chart are linear sRGB values. You can plug in your own colors to generate your own data.
In [1]:
import json
import brushcue
from pathlib import Path
from brushcue import ProfiledColor
painter_dimensions = brushcue.Vector2f.from_components(256, 256)
stroke_width = 2
working_color_profile = brushcue.ColorProfile.srgb()
output_color_profile = brushcue.ColorProfile.srgb()
ctx = brushcue.Context()
HOME = "/home/dito/dev/monorepo/writing/graphics/chapters/blend/assets"
def make_image(foreground: brushcue.RGBAColor, background: brushcue.RGBAColor, blend_fn) -> brushcue.Composition:
center_1 = brushcue.Point2f.from_components(82, 0)
center_2 = brushcue.Point2f.from_components(0, 0)
def make_blend_circles() -> brushcue.Composition:
painter_1 = brushcue.Painter.new()
painter_2 = brushcue.Painter.new()
render_style_1 = brushcue.RenderStyle.fill_only(
brushcue.Fill.solid(brushcue.ProfiledColor.from_rgba_srgb_linear(background))
)
render_style_2 = brushcue.RenderStyle.fill_only(
brushcue.Fill.solid(brushcue.ProfiledColor.from_rgba_srgb_linear(foreground))
)
instances = brushcue.Transform2.identity().to_list()
painter_1 = painter_1.add_ellipse_with_render_style( center_1, painter_dimensions, 0, render_style_1, instances)
painter_2 = painter_2.add_ellipse_with_render_style( center_2, painter_dimensions, 0, render_style_2, instances)
composition_1 = brushcue.Composition.painter(painter_1)
composition_2 = brushcue.Composition.painter(painter_2)
composition_1 = composition_1.color_convert(working_color_profile)
composition_2 = composition_2.color_convert(working_color_profile)
return blend_fn(composition_2, composition_1, brushcue.Transform2.identity())
def make_circle_outlines() -> brushcue.Composition:
brush = brushcue.Brush.solid(
brushcue.ProfiledColor.from_rgba_srgb(
brushcue.RGBAColor.from_components(0, 0, 0, 1)
),
stroke_width
)
painter_1 = brushcue.Painter.new()
painter_2 = brushcue.Painter.new()
render_style = brushcue.RenderStyle.brush_only(brush)
instances = brushcue.Transform2.identity().to_list()
painter_1 = painter_1.add_ellipse_with_render_style( center_1, painter_dimensions, 0, render_style, instances)
painter_2 = painter_2.add_ellipse_with_render_style( center_2, painter_dimensions, 0, render_style, instances)
composition_1 = brushcue.Composition.painter(painter_1)
composition_2 = brushcue.Composition.painter(painter_2)
composition_1 = composition_1.color_convert(working_color_profile)
composition_2 = composition_2.color_convert(working_color_profile)
return composition_1.blend_alpha(composition_2, brushcue.Transform2.identity())
blend_circles = make_blend_circles()
outlines = make_circle_outlines()
blended = outlines.blend_alpha(blend_circles, brushcue.Transform2.identity())
return blended.color_convert( output_color_profile)
In [2]:
colors = [
brushcue.RGBAColor.from_components(1, 1, 1, 1),
brushcue.RGBAColor.from_components(0, 0, 0, 1),
brushcue.RGBAColor.from_components(1, 0, 0, 1),
brushcue.RGBAColor.from_components(0, 1, 1, 1),
brushcue.RGBAColor.from_components(0.6, 0.9, 0.4, 1),
brushcue.RGBAColor.from_components(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 [3]:
# 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.to_image_bytes(ctx)
path = Path(f"{HOME}/{name}/{foreground_idx}/{background_idx}.png")
foreground_color = foreground.execute(ctx)
background_color = background.execute(ctx)
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)
In [4]:
# Generates the Individual Swatches
for (idx, color) in enumerate(colors):
painter = brushcue.Painter.new()
render_style = brushcue.RenderStyle.brush_and_fill(
brushcue.Brush.solid(
brushcue.ProfiledColor.from_rgba_srgb(
brushcue.RGBAColor.from_components(0, 0, 0, 1)
),
stroke_width
),
brushcue.Fill.solid(brushcue.ProfiledColor.from_rgba_srgb_linear(color))
)
instances = brushcue.Transform2.identity().to_list()
painter = painter.add_ellipse_with_render_style(brushcue.Point2f.from_components(0, 0), painter_dimensions, 0, render_style, instances)
composition = brushcue.Composition.painter(painter)
composition = composition.color_convert( output_color_profile)
result = composition.execute(ctx)
output_bytes = result.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)
color_json = {
"color": format_channels(rgba)
}
with open(f"{HOME}/swatches/color_{idx}.json", "w") as f:
json.dump(color_json, f)