Files
OmaPhoto/src/Document/Curves.cpp
T
雷电芽衣andClaude c864304695
Release / deb (push) Canceled after 0s
Release / rpm (push) Canceled after 0s
Release / arch (push) Canceled after 0s
Release / publish (push) Canceled after 0s
OmaPhoto 1.0.0: the Linux port of Compositor
A layered image editor for Linux and Omarchy, ported from Compositor
(github.com/robbietilton/Compositor), whose Swift source is its
specification. It reads and writes Compositor's .comp projects.

Packages for Ubuntu, Fedora and Arch come from the release workflow;
scripts/install/ holds one install script per distribution family,
and flake.nix serves NixOS.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-26 00:44:52 -04:00

68 lines
2.7 KiB
C++

#include "Document/Curves.h"
#include "Document/BrushStroke.h"
#include "IO/ProjectStore.h"
#include <QPainter>
#include <algorithm>
#include <array>
#include <cmath>
extern "C" {
#include "LevelsPixels.h"
}
bool CurvesSettings::isValid() const
{
// Fixed ends and rising x keep x in range.
return channels.size() == 4 && std::all_of(channels.begin(), channels.end(), [](const std::vector<CurvePoint> &points) {
return points.size() >= 2 && points.size() <= 32 && points.front().x == 0 && points.back().x == 255
&& std::all_of(points.begin(), points.end(), [](const CurvePoint &point) { return point.y >= 0 && point.y <= 255; })
&& std::adjacent_find(points.begin(), points.end(), [](const CurvePoint &a, const CurvePoint &b) { return !(a.x < b.x); }) == points.end();
});
}
double CurvesSettings::value(double x, size_t channel) const
{
const std::vector<CurvePoint> &p = channels.at(channel);
// Swift's lastIndex(where:), else the first segment.
size_t last = 0;
for (size_t index = 0; index < p.size(); ++index) {
if (p[index].x <= x)
last = index;
}
const size_t i = std::min(p.size() - 2, last);
std::vector<double> d;
for (size_t index = 0; index + 1 < p.size(); ++index)
d.push_back((p[index + 1].y - p[index].y) / (p[index + 1].x - p[index].x));
const auto slope = [&](size_t j) {
if (j == 0)
return d.front();
if (j == p.size() - 1)
return d.back();
if (d[j - 1] * d[j] <= 0)
return 0.0;
return 2 / (1 / d[j - 1] + 1 / d[j]);
};
const double h = p[i + 1].x - p[i].x, t = std::min(1.0, std::max(0.0, (x - p[i].x) / h));
const double y = (2 * t * t * t - 3 * t * t + 1) * p[i].y + (t * t * t - 2 * t * t + t) * h * slope(i)
+ (-2 * t * t * t + 3 * t * t) * p[i + 1].y + (t * t * t - t * t) * h * slope(i + 1);
return std::min(255.0, std::max(0.0, y));
}
QImage CurvesSettings::apply(const QImage &image) const
{
if (!isValid())
throw ProjectError(ProjectError::Kind::invalid);
QImage context = BrushRaster::context(image.width(), image.height(), false);
{
QPainter painter(&context);
BrushRaster::draw(image, QRectF(0, 0, image.width(), image.height()), painter);
}
// Each colour's curve, then the composite's.
std::array<float, 3 * 256> tables;
for (size_t channel = 1; channel <= 3; ++channel) {
for (size_t x = 0; x < 256; ++x)
tables[(channel - 1) * 256 + x] = float(value(value(double(x), channel), 0) / 255);
}
levels_apply(context.bits(), size_t(image.width()) * size_t(image.height()), tables.data());
return context;
}