#include "paint/blend.h" #include #include namespace pp::paint { namespace { [[nodiscard]] float saturate(float value) noexcept { if (!std::isfinite(value)) { return value < 0.0F ? 0.0F : 1.0F; } return std::clamp(value, 0.0F, 1.0F); } [[nodiscard]] float mix(float a, float b, float t) noexcept { return a * (1.0F - t) + b * t; } [[nodiscard]] float blend_channel(float base, float stroke, BlendMode mode) noexcept { switch (mode) { case BlendMode::normal: return stroke; case BlendMode::multiply: return base * stroke; case BlendMode::screen: return 1.0F - (1.0F - base) * (1.0F - stroke); case BlendMode::color_dodge: if (stroke >= 1.0F) { return 1.0F; } return saturate(base / (1.0F - stroke)); case BlendMode::overlay: return base < 0.5F ? 2.0F * base * stroke : 1.0F - 2.0F * (1.0F - base) * (1.0F - stroke); } return stroke; } [[nodiscard]] float blend_rgb(float base, float stroke, float base_alpha, float stroke_alpha, float alpha_total, BlendMode mode) noexcept { if (alpha_total <= 0.0F) { return 0.0F; } const auto stroke_weight = stroke_alpha / alpha_total; const auto base_weight = base_alpha / alpha_total; if (mode == BlendMode::normal) { return saturate(mix(base, stroke, stroke_weight)); } const auto mode_value = blend_channel(base, stroke, mode); return saturate(mix(stroke, mix(base, mode_value, stroke_weight), base_weight)); } } Rgba blend_pixels(Rgba base, Rgba stroke, BlendMode mode) noexcept { base.r = saturate(base.r); base.g = saturate(base.g); base.b = saturate(base.b); base.a = saturate(base.a); stroke.r = saturate(stroke.r); stroke.g = saturate(stroke.g); stroke.b = saturate(stroke.b); stroke.a = saturate(stroke.a); if (stroke.a == 0.0F) { return base; } const auto contribution = (1.0F - base.a) * stroke.a; const auto alpha_total = saturate(base.a + contribution); return { blend_rgb(base.r, stroke.r, base.a, stroke.a, alpha_total, mode), blend_rgb(base.g, stroke.g, base.a, stroke.a, alpha_total, mode), blend_rgb(base.b, stroke.b, base.a, stroke.a, alpha_total, mode), alpha_total, }; } const char* blend_mode_name(BlendMode mode) noexcept { switch (mode) { case BlendMode::normal: return "normal"; case BlendMode::multiply: return "multiply"; case BlendMode::screen: return "screen"; case BlendMode::color_dodge: return "color_dodge"; case BlendMode::overlay: return "overlay"; } return "unknown"; } }