110 lines
2.7 KiB
C++
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";
|
|
}
|
|
|
|
}
|