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>
This commit is contained in:
commit
c864304695
564 files changed
+100804
No files matched your search
@@ -0,0 +1,67 @@
|
||||
#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;
|
||||
}
|
||||
Reference in new issue
Block a user