Quit settles a pending gradient, a pixel move and open dialogs; Dither › Scanlines (CRT) on all cores (13.17)
This commit is contained in:
1 parent
765e071c0c
commit
5bfbc6cd5d
12 files changed
+913
-32
No files matched your search
+62
-5
@@ -1,17 +1,28 @@
|
||||
#include "Document/Dither.h"
|
||||
#include "Document/BrushStroke.h"
|
||||
#include "Document/PixelAdjust.h"
|
||||
#include "IO/ImageExporter.h"
|
||||
#include "Rendering/PoolMap.h"
|
||||
#include <QFontMetricsF>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
extern "C" {
|
||||
#include "DitherPixels.h"
|
||||
}
|
||||
|
||||
// The kernel's bands, where Swift's kernel calls dispatch_apply.
|
||||
extern "C" void dither_bands(size_t bands, void (*band)(void *context, size_t index), void *context)
|
||||
{
|
||||
std::vector<size_t> indices(bands);
|
||||
std::iota(indices.begin(), indices.end(), size_t(0));
|
||||
PoolMap::blocking(indices, [band, context](size_t index) { band(context, index); });
|
||||
}
|
||||
|
||||
// Swift's Dither.swift: the settings, the chunky pixels, the glyphs.
|
||||
namespace {
|
||||
// Swift's Characters: the text's graphemes.
|
||||
@@ -53,6 +64,7 @@ QString rawValue(DitherStyle style)
|
||||
case DitherStyle::diamonds: return QStringLiteral("Halftone Diamonds");
|
||||
case DitherStyle::patterns: return QStringLiteral("Mac Patterns");
|
||||
case DitherStyle::ascii: return QStringLiteral("ASCII");
|
||||
case DitherStyle::scanlines: return QStringLiteral("Scanlines (CRT)");
|
||||
}
|
||||
throw std::logic_error("unknown dither style");
|
||||
}
|
||||
@@ -80,7 +92,12 @@ bool isHalftone(DitherStyle style)
|
||||
|
||||
bool drawsMarks(DitherStyle style)
|
||||
{
|
||||
return !hasTones(style);
|
||||
return !hasTones(style) && style != DitherStyle::scanlines;
|
||||
}
|
||||
|
||||
bool usesPixelSize(DitherStyle style)
|
||||
{
|
||||
return style != DitherStyle::ascii && style != DitherStyle::scanlines;
|
||||
}
|
||||
|
||||
QString rawValue(DitherPixelShape shape)
|
||||
@@ -105,6 +122,10 @@ DitherSettings DitherSettings::normalized() const
|
||||
result.pixelSize = std::round(clamp(pixelSize, pixelSizeLow, pixelSizeHigh, 2));
|
||||
result.cellSize = std::round(clamp(cellSize, cellSizeLow, cellSizeHigh, 8));
|
||||
result.textSize = std::round(clamp(textSize, textSizeLow, textSizeHigh, 14));
|
||||
result.lineSpacing = std::round(clamp(lineSpacing, lineSpacingLow, lineSpacingHigh, 4));
|
||||
result.glow = clamp(glow, 0, 100, 35);
|
||||
result.dots = clamp(dots, 0, 100, 0);
|
||||
result.wobble = clamp(wobble, wobbleLow, wobbleHigh, 0);
|
||||
result.angle = clamp(angle, -90, 90, 45);
|
||||
result.levels = std::round(clamp(levels, levelsLow, levelsHigh, 2));
|
||||
result.diffusion = clamp(diffusion, 0, 100, 100);
|
||||
@@ -125,8 +146,8 @@ DitherSettings DitherSettings::normalized() const
|
||||
QImage DitherSettings::apply(const QImage &image) const
|
||||
{
|
||||
const DitherSettings settings = normalized();
|
||||
// ASCII draws at full resolution: chunks would blur its letters.
|
||||
const int block = settings.style == DitherStyle::ascii ? 1 : int(settings.pixelSize);
|
||||
// ASCII and Scanlines draw full size: chunks would blur them.
|
||||
const int block = usesPixelSize(settings.style) ? int(settings.pixelSize) : 1;
|
||||
const QImage source = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
|
||||
if (source.isNull())
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
@@ -153,6 +174,8 @@ QImage DitherSettings::apply(const QImage &image) const
|
||||
}
|
||||
}
|
||||
const QImage dithered = settings.dither(working);
|
||||
if (settings.style == DitherStyle::scanlines && settings.glow > 0)
|
||||
return settings.glowing(dithered);
|
||||
if (block <= 1)
|
||||
return dithered;
|
||||
// Blown back up without smoothing.
|
||||
@@ -171,9 +194,42 @@ QImage DitherSettings::apply(const QImage &image) const
|
||||
return full;
|
||||
}
|
||||
|
||||
// The lines' light blurred wide and added back: phosphor bloom.
|
||||
QImage DitherSettings::glowing(const QImage &image) const
|
||||
{
|
||||
// Blurred small, scaled back up: same light, less work.
|
||||
const double sigma = lineSpacing * 3 + 3, shrink = std::max(1.0, std::floor(sigma / 4));
|
||||
const int width = image.width(), height = image.height();
|
||||
// Swift clamps first: edges repeat outward before shrinking.
|
||||
const int step = int(shrink), pad = int(std::ceil(sigma * 3 / shrink)) * step;
|
||||
QImage padded = BrushRaster::context(width + 2 * pad, height + 2 * pad, false);
|
||||
for (int y = 0; y < padded.height(); ++y) {
|
||||
const uchar *source = image.constScanLine(std::clamp(y - pad, 0, height - 1));
|
||||
uchar *out = padded.scanLine(y);
|
||||
for (int x = 0; x < padded.width(); ++x)
|
||||
std::copy_n(source + std::clamp(x - pad, 0, width - 1) * 4, 4, out + x * 4);
|
||||
}
|
||||
const int smallWidth = (padded.width() + step - 1) / step, smallHeight = (padded.height() + step - 1) / step;
|
||||
const QImage small = padded.scaled(smallWidth, smallHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
if (small.isNull())
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
const QImage blurred = PixelAdjust::gaussianBlur(small, sigma / shrink, true)
|
||||
.scaled(smallWidth * step, smallHeight * step, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)
|
||||
.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
|
||||
QImage result = image.copy();
|
||||
if (blurred.isNull() || result.isNull())
|
||||
throw ExportError(ExportError::Kind::render);
|
||||
// The scaled light's own rows, cropped to the image.
|
||||
QImage bloom = BrushRaster::context(width, height, false);
|
||||
for (int y = 0; y < height; ++y)
|
||||
std::copy_n(blurred.constScanLine(y + pad) + pad * 4, width * 4, bloom.scanLine(y));
|
||||
dither_glow(result.bits(), bloom.constBits(), size_t(width), size_t(height), size_t(result.bytesPerLine()), float(glow / 100 * 2.5));
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage DitherSettings::dither(const QImage &image) const
|
||||
{
|
||||
const int cell = int(cellSize);
|
||||
const int cell = int(style == DitherStyle::scanlines ? lineSpacing : cellSize);
|
||||
const Glyphs glyphs = style == DitherStyle::ascii ? DitherSettings::glyphs(characters.isEmpty() ? defaultCharacters() : characters, int(textSize)) : Glyphs();
|
||||
const bool two = colors == DitherColors::twoColors;
|
||||
const std::array<uint8_t, 3> darkColor = two ? bytes(dark) : std::array<uint8_t, 3>{0, 0, 0};
|
||||
@@ -184,7 +240,8 @@ QImage DitherSettings::dither(const QImage &image) const
|
||||
.contrast = float(contrast / 100), .cell = cell, .angle = float(angle * M_PI / 180), .lightOnDark = lightOnDark ? 1 : 0,
|
||||
.originalColors = colors == DitherColors::original ? 1 : 0, .dark = {darkColor[0], darkColor[1], darkColor[2]},
|
||||
.light = {lightColor[0], lightColor[1], lightColor[2]}, .glyphWidth = glyphs.width, .glyphHeight = glyphs.height, .glyphs = glyphs.maps.data(),
|
||||
.glyphCoverage = glyphs.coverage.data(), .glyphCount = int(glyphs.coverage.size())};
|
||||
.glyphCoverage = glyphs.coverage.data(), .glyphCount = int(glyphs.coverage.size()), .dots = float(dots / 100),
|
||||
.wobble = float(wobble)};
|
||||
failed = dither_apply(pixels, size_t(width), size_t(height), size_t(stride), ¶ms) == 0;
|
||||
});
|
||||
if (failed)
|
||||
|
||||
Reference in new issue
Block a user