Add paint blend reference tests
This commit is contained in:
109
src/paint/blend.cpp
Normal file
109
src/paint/blend.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#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";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user