Photoshop's remaining blend modes in its groups (12.7a); v1.2.2 kernels for 12.7b
This commit is contained in:
1 parent
29eab10edc
commit
7d47cc367a
26 files changed
+348
-56
No files matched your search
@@ -6,12 +6,15 @@
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
const std::array<std::pair<LayerBlendMode, const char *>, 14> names{{
|
||||
{LayerBlendMode::normal, "Normal"}, {LayerBlendMode::multiply, "Multiply"}, {LayerBlendMode::screen, "Screen"},
|
||||
{LayerBlendMode::overlay, "Overlay"}, {LayerBlendMode::softLight, "Soft Light"}, {LayerBlendMode::darken, "Darken"}, {LayerBlendMode::lighten, "Lighten"},
|
||||
{LayerBlendMode::difference, "Difference"}, {LayerBlendMode::colorDodge, "Color Dodge"},
|
||||
{LayerBlendMode::colorBurn, "Color Burn"}, {LayerBlendMode::hue, "Hue"}, {LayerBlendMode::saturation, "Saturation"},
|
||||
{LayerBlendMode::color, "Color"}, {LayerBlendMode::luminosity, "Luminosity"},
|
||||
const std::array<std::pair<LayerBlendMode, const char *>, 24> names{{
|
||||
{LayerBlendMode::normal, "Normal"}, {LayerBlendMode::darken, "Darken"}, {LayerBlendMode::multiply, "Multiply"},
|
||||
{LayerBlendMode::colorBurn, "Color Burn"}, {LayerBlendMode::linearBurn, "Linear Burn"}, {LayerBlendMode::lighten, "Lighten"},
|
||||
{LayerBlendMode::screen, "Screen"}, {LayerBlendMode::colorDodge, "Color Dodge"}, {LayerBlendMode::linearDodge, "Linear Dodge (Add)"},
|
||||
{LayerBlendMode::overlay, "Overlay"}, {LayerBlendMode::softLight, "Soft Light"}, {LayerBlendMode::hardLight, "Hard Light"},
|
||||
{LayerBlendMode::vividLight, "Vivid Light"}, {LayerBlendMode::linearLight, "Linear Light"}, {LayerBlendMode::pinLight, "Pin Light"},
|
||||
{LayerBlendMode::hardMix, "Hard Mix"}, {LayerBlendMode::difference, "Difference"}, {LayerBlendMode::exclusion, "Exclusion"},
|
||||
{LayerBlendMode::subtract, "Subtract"}, {LayerBlendMode::divide, "Divide"}, {LayerBlendMode::hue, "Hue"},
|
||||
{LayerBlendMode::saturation, "Saturation"}, {LayerBlendMode::color, "Color"}, {LayerBlendMode::luminosity, "Luminosity"},
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -20,6 +23,16 @@ QString rawValue(LayerBlendMode mode)
|
||||
return QString::fromLatin1(names.at(size_t(mode)).second);
|
||||
}
|
||||
|
||||
int blendGroup(LayerBlendMode mode)
|
||||
{
|
||||
// Each group's modes sit together in the enum.
|
||||
for (const auto &[first, group] : std::array<std::pair<LayerBlendMode, int>, 5>{{{LayerBlendMode::hue, 5},
|
||||
{LayerBlendMode::difference, 4}, {LayerBlendMode::overlay, 3}, {LayerBlendMode::lighten, 2}, {LayerBlendMode::darken, 1}}})
|
||||
if (mode >= first)
|
||||
return group;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<LayerBlendMode> layerBlendMode(const QString &rawValue)
|
||||
{
|
||||
for (const auto &[mode, name] : names) {
|
||||
|
||||
@@ -4,16 +4,24 @@
|
||||
#include <optional>
|
||||
|
||||
enum class LayerBlendMode {
|
||||
normal, multiply, screen, overlay, softLight, darken, lighten, difference,
|
||||
colorDodge, colorBurn, hue, saturation, color, luminosity
|
||||
normal,
|
||||
darken, multiply, colorBurn, linearBurn,
|
||||
lighten, screen, colorDodge, linearDodge,
|
||||
overlay, softLight, hardLight, vividLight, linearLight, pinLight, hardMix,
|
||||
difference, exclusion, subtract, divide,
|
||||
hue, saturation, color, luminosity
|
||||
};
|
||||
|
||||
// Swift's `groups`, Photoshop's: the menu draws a line between.
|
||||
int blendGroup(LayerBlendMode mode);
|
||||
|
||||
// Swift's `allCases`: the order of the blend menu.
|
||||
inline constexpr std::array allLayerBlendModes{
|
||||
LayerBlendMode::normal, LayerBlendMode::multiply, LayerBlendMode::screen, LayerBlendMode::overlay,
|
||||
LayerBlendMode::softLight, LayerBlendMode::darken, LayerBlendMode::lighten, LayerBlendMode::difference, LayerBlendMode::colorDodge,
|
||||
LayerBlendMode::colorBurn, LayerBlendMode::hue, LayerBlendMode::saturation, LayerBlendMode::color,
|
||||
LayerBlendMode::luminosity};
|
||||
LayerBlendMode::normal, LayerBlendMode::darken, LayerBlendMode::multiply, LayerBlendMode::colorBurn, LayerBlendMode::linearBurn,
|
||||
LayerBlendMode::lighten, LayerBlendMode::screen, LayerBlendMode::colorDodge, LayerBlendMode::linearDodge,
|
||||
LayerBlendMode::overlay, LayerBlendMode::softLight, LayerBlendMode::hardLight, LayerBlendMode::vividLight, LayerBlendMode::linearLight,
|
||||
LayerBlendMode::pinLight, LayerBlendMode::hardMix, LayerBlendMode::difference, LayerBlendMode::exclusion, LayerBlendMode::subtract,
|
||||
LayerBlendMode::divide, LayerBlendMode::hue, LayerBlendMode::saturation, LayerBlendMode::color, LayerBlendMode::luminosity};
|
||||
|
||||
// The name users see and the manifest stores.
|
||||
QString rawValue(LayerBlendMode mode);
|
||||
|
||||
@@ -31,6 +31,12 @@ std::optional<LayerBlendMode> fromPSD(const QString &key)
|
||||
{QStringLiteral("div "), LayerBlendMode::colorDodge}, {QStringLiteral("idiv"), LayerBlendMode::colorBurn},
|
||||
{QStringLiteral("hue "), LayerBlendMode::hue}, {QStringLiteral("sat "), LayerBlendMode::saturation},
|
||||
{QStringLiteral("colr"), LayerBlendMode::color}, {QStringLiteral("lum "), LayerBlendMode::luminosity},
|
||||
{QStringLiteral("lbrn"), LayerBlendMode::linearBurn}, {QStringLiteral("lddg"), LayerBlendMode::linearDodge},
|
||||
{QStringLiteral("hLit"), LayerBlendMode::hardLight}, {QStringLiteral("vLit"), LayerBlendMode::vividLight},
|
||||
{QStringLiteral("lLit"), LayerBlendMode::linearLight}, {QStringLiteral("pLit"), LayerBlendMode::pinLight},
|
||||
{QStringLiteral("hMix"), LayerBlendMode::hardMix}, {QStringLiteral("smud"), LayerBlendMode::exclusion},
|
||||
{QStringLiteral("fsub"), LayerBlendMode::subtract}, {QStringLiteral("fdiv"), LayerBlendMode::divide},
|
||||
// Dissolve, Darker Color and Lighter Color have no twin: Normal.
|
||||
};
|
||||
if (!modes.contains(key))
|
||||
return std::nullopt;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Rendering/HslBlend.h"
|
||||
#include "Rendering/PoolMap.h"
|
||||
#include "Rendering/SeparableBlend.h"
|
||||
#include "IO/ImageExporter.h"
|
||||
#include <QtConcurrent>
|
||||
#include <algorithm>
|
||||
@@ -66,7 +67,8 @@ Color mixed(LayerBlendMode mode, const Color &backdrop, const Color &source)
|
||||
case LayerBlendMode::luminosity:
|
||||
return setLum(backdrop, lum(source));
|
||||
default:
|
||||
throw std::logic_error("HslBlend handles only the four non-separable modes");
|
||||
return {SeparableBlend::channel(mode, backdrop[0], source[0]), SeparableBlend::channel(mode, backdrop[1], source[1]),
|
||||
SeparableBlend::channel(mode, backdrop[2], source[2])};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +76,7 @@ Color mixed(LayerBlendMode mode, const Color &backdrop, const Color &source)
|
||||
bool HslBlend::handles(LayerBlendMode mode)
|
||||
{
|
||||
return mode == LayerBlendMode::hue || mode == LayerBlendMode::saturation || mode == LayerBlendMode::color
|
||||
|| mode == LayerBlendMode::luminosity;
|
||||
|| mode == LayerBlendMode::luminosity || SeparableBlend::needsSurface(mode);
|
||||
}
|
||||
|
||||
QImage HslBlend::blend(const QImage &backdrop, const QImage &source, LayerBlendMode mode, double opacity)
|
||||
@@ -84,7 +86,7 @@ QImage HslBlend::blend(const QImage &backdrop, const QImage &source, LayerBlendM
|
||||
throw std::logic_error("HslBlend needs two RGBA8888 premultiplied images of one size");
|
||||
// Checked here: a worker's exception would come back wrapped.
|
||||
if (!handles(mode))
|
||||
throw std::logic_error("HslBlend blends hue, saturation, color and luminosity alone");
|
||||
throw std::logic_error("HslBlend blends only the modes QPainter lacks");
|
||||
QImage result(backdrop.size(), QImage::Format_RGBA8888_Premultiplied);
|
||||
if (result.isNull())
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "Document/LayerAppearance.h"
|
||||
#include <QImage>
|
||||
|
||||
// Hue, Saturation, Color, Luminosity: PDF blend modes QPainter lacks.
|
||||
// Blend modes QPainter lacks: the four non-separable, SeparableBlend's eight.
|
||||
namespace HslBlend {
|
||||
bool handles(LayerBlendMode mode);
|
||||
// Source over backdrop, same size, both RGBA8888 premultiplied.
|
||||
|
||||
@@ -32,6 +32,10 @@ QPainter::CompositionMode compositionMode(LayerBlendMode mode)
|
||||
return QPainter::CompositionMode_ColorDodge;
|
||||
case LayerBlendMode::colorBurn:
|
||||
return QPainter::CompositionMode_ColorBurn;
|
||||
case LayerBlendMode::hardLight:
|
||||
return QPainter::CompositionMode_HardLight;
|
||||
case LayerBlendMode::exclusion:
|
||||
return QPainter::CompositionMode_Exclusion;
|
||||
default:
|
||||
throw std::logic_error("QPainter has no composition mode for this blend mode");
|
||||
}
|
||||
@@ -214,7 +218,7 @@ void LayerRenderer::composite(QPainter &context, const QTransform &placement, co
|
||||
if (byHand) {
|
||||
const QImage *target = dynamic_cast<const QImage *>(context.device());
|
||||
if (!target)
|
||||
throw std::logic_error("hue, saturation, color and luminosity need an image-backed painter");
|
||||
throw std::logic_error("modes blended by hand need an image-backed painter");
|
||||
const QImage backdrop = target->copy(area).convertToFormat(QImage::Format_RGBA8888_Premultiplied);
|
||||
if (backdrop.isNull())
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "Rendering/SeparableBlend.h"
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
// A zero divisor gives infinity; the clamp takes it home.
|
||||
float burn(float backdrop, float source)
|
||||
{
|
||||
if (backdrop >= 1)
|
||||
return 1;
|
||||
return 1 - std::min(1.0f, (1 - backdrop) / source);
|
||||
}
|
||||
|
||||
float dodge(float backdrop, float source)
|
||||
{
|
||||
if (backdrop <= 0)
|
||||
return 0;
|
||||
return std::min(1.0f, backdrop / (1 - source));
|
||||
}
|
||||
}
|
||||
|
||||
bool SeparableBlend::needsSurface(LayerBlendMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case LayerBlendMode::linearBurn:
|
||||
case LayerBlendMode::linearDodge:
|
||||
case LayerBlendMode::vividLight:
|
||||
case LayerBlendMode::linearLight:
|
||||
case LayerBlendMode::pinLight:
|
||||
case LayerBlendMode::hardMix:
|
||||
case LayerBlendMode::subtract:
|
||||
case LayerBlendMode::divide:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float SeparableBlend::channel(LayerBlendMode mode, float backdrop, float source)
|
||||
{
|
||||
switch (mode) {
|
||||
case LayerBlendMode::linearBurn:
|
||||
return std::max(0.0f, backdrop + source - 1);
|
||||
case LayerBlendMode::linearDodge:
|
||||
return std::min(1.0f, backdrop + source);
|
||||
case LayerBlendMode::vividLight:
|
||||
return source <= 0.5f ? burn(backdrop, 2 * source) : dodge(backdrop, 2 * source - 1);
|
||||
case LayerBlendMode::linearLight:
|
||||
return std::clamp(backdrop + 2 * source - 1, 0.0f, 1.0f);
|
||||
case LayerBlendMode::pinLight:
|
||||
return source <= 0.5f ? std::min(backdrop, 2 * source) : std::max(backdrop, 2 * source - 1);
|
||||
case LayerBlendMode::hardMix:
|
||||
return backdrop + source >= 1 ? 1 : 0;
|
||||
case LayerBlendMode::subtract:
|
||||
return std::max(0.0f, backdrop - source);
|
||||
case LayerBlendMode::divide:
|
||||
return source <= 0 ? 1 : std::min(1.0f, backdrop / source);
|
||||
default:
|
||||
throw std::logic_error("SeparableBlend blends only the modes QPainter lacks");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "Document/LayerAppearance.h"
|
||||
|
||||
// Swift's SeparableBlend: channel-by-channel modes QPainter lacks.
|
||||
namespace SeparableBlend {
|
||||
// Blended by hand against a surface, Swift's `needsSurface`.
|
||||
bool needsSurface(LayerBlendMode mode);
|
||||
// Photoshop's formula on one channel: backdrop, source, 0 to 1.
|
||||
float channel(LayerBlendMode mode, float backdrop, float source);
|
||||
}
|
||||
@@ -4,8 +4,12 @@ BlendModePicker::BlendModePicker(EditorSession &session, QWidget *parent) : QCom
|
||||
{
|
||||
setAccessibleName(QStringLiteral("Blend mode"));
|
||||
setObjectName(QStringLiteral("blendMode"));
|
||||
for (const LayerBlendMode mode : allLayerBlendModes)
|
||||
// Grouped as Photoshop groups them, a line between.
|
||||
for (const LayerBlendMode mode : allLayerBlendModes) {
|
||||
if (count() && blendGroup(mode) != blendGroup(*layerBlendMode(itemText(count() - 1))))
|
||||
insertSeparator(count());
|
||||
addItem(rawValue(mode));
|
||||
}
|
||||
connect(this, &QComboBox::highlighted, this, &BlendModePicker::highlight);
|
||||
connect(this, &QComboBox::activated, this, &BlendModePicker::choose);
|
||||
connect(&m_session, &EditorSession::changed, this, &BlendModePicker::synchronize);
|
||||
|
||||
Reference in new issue
Block a user