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,157 @@
|
||||
#include "Document/LayerAdjustment.h"
|
||||
#include "Document/EditorSession.h"
|
||||
#include <QRandomGenerator>
|
||||
#include <cmath>
|
||||
|
||||
QString rawValue(AdjustmentKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case AdjustmentKind::hsv: return QStringLiteral("Hue/Saturation");
|
||||
case AdjustmentKind::levels: return QStringLiteral("Levels");
|
||||
case AdjustmentKind::curves: return QStringLiteral("Curves");
|
||||
case AdjustmentKind::exposure: return QStringLiteral("Exposure");
|
||||
case AdjustmentKind::gradientMap: return QStringLiteral("Gradient Map");
|
||||
case AdjustmentKind::grain: return QStringLiteral("Grain");
|
||||
}
|
||||
throw std::logic_error("unknown adjustment kind");
|
||||
}
|
||||
|
||||
std::optional<AdjustmentKind> adjustmentKind(const QString &text)
|
||||
{
|
||||
for (const AdjustmentKind kind : allAdjustmentKinds) {
|
||||
if (rawValue(kind) == text)
|
||||
return kind;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<FilterKind> filterKind(AdjustmentKind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case AdjustmentKind::curves: return FilterKind::curves;
|
||||
case AdjustmentKind::exposure: return FilterKind::exposure;
|
||||
case AdjustmentKind::gradientMap: return FilterKind::gradientMap;
|
||||
case AdjustmentKind::grain: return FilterKind::grain;
|
||||
case AdjustmentKind::hsv:
|
||||
case AdjustmentKind::levels: return std::nullopt;
|
||||
}
|
||||
throw std::logic_error("unknown adjustment kind");
|
||||
}
|
||||
|
||||
HueSaturationSettings LayerAdjustment::resolvedHSV() const
|
||||
{
|
||||
return hsvSettings.value_or(HueSaturationSettings(hue, saturation, lightness, colorize));
|
||||
}
|
||||
|
||||
ExposureSettings LayerAdjustment::exposure() const
|
||||
{
|
||||
return exposureSettings.value_or(ExposureSettings());
|
||||
}
|
||||
|
||||
void LayerAdjustment::setExposure(const ExposureSettings &value)
|
||||
{
|
||||
exposureSettings = value;
|
||||
}
|
||||
|
||||
GradientMapSettings LayerAdjustment::gradientMap() const
|
||||
{
|
||||
return gradientMapSettings.value_or(GradientMapSettings());
|
||||
}
|
||||
|
||||
void LayerAdjustment::setGradientMap(const GradientMapSettings &value)
|
||||
{
|
||||
gradientMapSettings = value;
|
||||
}
|
||||
|
||||
GrainSettings LayerAdjustment::grain() const
|
||||
{
|
||||
return grainSettings.value_or(GrainSettings());
|
||||
}
|
||||
|
||||
void LayerAdjustment::setGrain(const GrainSettings &value)
|
||||
{
|
||||
grainSettings = value;
|
||||
}
|
||||
|
||||
// Bounds refuse what is no number: no isFinite terms.
|
||||
bool LayerAdjustment::isValid() const
|
||||
{
|
||||
const auto within = [](const RangeAdjustment &values) {
|
||||
return std::abs(values.hue) <= 360 && std::abs(values.saturation) <= 100 && std::abs(values.lightness) <= 100;
|
||||
};
|
||||
if (!within(RangeAdjustment{hue, saturation, lightness}))
|
||||
return false;
|
||||
const HueSaturationSettings hsv = resolvedHSV();
|
||||
for (const auto &[range, values] : hsv.adjustments) {
|
||||
if (!within(values))
|
||||
return false;
|
||||
}
|
||||
for (const auto &[range, band] : hsv.bands) {
|
||||
for (const double handle : band.handles()) {
|
||||
if (!std::isfinite(handle))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const LevelRange &range : levels.ranges) {
|
||||
if (!(range == range.normalized()))
|
||||
return false;
|
||||
}
|
||||
return curves.isValid() && exposure().isValid() && gradientMap().isValid() && grain().isValid();
|
||||
}
|
||||
|
||||
QImage LayerAdjustment::apply(const QImage &image, std::optional<QRectF> region) const
|
||||
{
|
||||
switch (kind) {
|
||||
case AdjustmentKind::hsv:
|
||||
return HueSaturationFilter::run(HueSaturationJob{image, resolvedHSV(), std::nullopt, QTransform(), false}).image;
|
||||
case AdjustmentKind::levels: return LevelsFilter::run(LevelsJob{image, levels, std::nullopt, QTransform()});
|
||||
case AdjustmentKind::curves: return curves.apply(image);
|
||||
case AdjustmentKind::exposure: return exposure().apply(image);
|
||||
case AdjustmentKind::gradientMap: return gradientMap().apply(image);
|
||||
case AdjustmentKind::grain: {
|
||||
// Grain sits in the document, however the image is cut.
|
||||
const QRectF area = region.value_or(QRectF(0, 0, image.width(), image.height()));
|
||||
return grain().apply(image, area.topLeft(), area.width() / std::max(1, image.width()));
|
||||
}
|
||||
}
|
||||
throw std::logic_error("unknown adjustment kind");
|
||||
}
|
||||
|
||||
void EditorSession::addAdjustment(AdjustmentKind kind)
|
||||
{
|
||||
if (!canEditLayers() || m_document->layers.size() >= 10'000)
|
||||
return;
|
||||
ImageLayer layer(rawValue(kind), m_document->size());
|
||||
LayerAdjustment adjustment{kind};
|
||||
// A Gradient Map runs from foreground to background, as Photoshop's.
|
||||
if (kind == AdjustmentKind::gradientMap)
|
||||
adjustment.setGradientMap(GradientMapSettings{AdjustmentColor(foregroundColor()), AdjustmentColor(m_backgroundColor)});
|
||||
// Each Grain layer rolls a pattern of its own.
|
||||
if (kind == AdjustmentKind::grain) {
|
||||
GrainSettings grain = adjustment.grain();
|
||||
grain.seed = QRandomGenerator::global()->generate();
|
||||
adjustment.setGrain(grain);
|
||||
}
|
||||
layer.adjustment = adjustment;
|
||||
const std::optional<ImageLayer> active = activeLayer();
|
||||
layer.parentID = active && active->isGroup ? m_activeLayerID : active ? active->parentID : std::nullopt;
|
||||
const int activeIndex = indexOf(m_document->layers, m_activeLayerID);
|
||||
const int index = activeIndex >= 0 ? activeIndex + 1 : int(m_document->layers.size());
|
||||
beginEdit(QStringLiteral("New %1 Adjustment").arg(rawValue(kind)));
|
||||
m_document->layers.insert(m_document->layers.begin() + index, layer);
|
||||
if (layer.parentID)
|
||||
m_collapsedGroupIDs.remove(*layer.parentID);
|
||||
setActiveLayerID(layer.id);
|
||||
endEdit();
|
||||
setAdjustmentEditingID(layer.id);
|
||||
}
|
||||
|
||||
void EditorSession::updateAdjustment(QUuid id, const LayerAdjustment &value)
|
||||
{
|
||||
const int index = m_document ? indexOf(m_document->layers, id) : -1;
|
||||
if (index < 0 || !value.isValid())
|
||||
return;
|
||||
m_document->layers[size_t(index)].adjustment = value;
|
||||
++m_brushRevision;
|
||||
notify();
|
||||
}
|
||||
Reference in new issue
Block a user