Files
panopainter/src/paint/blend.cpp

110 lines
2.7 KiB
C++

#include "paint/blend.h"
#include <algorithm>
#include <cmath>
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";
}
}