Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/config/generalconf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ GeneralConf::GeneralConf(QWidget* parent)
initCopyPathAfterSave();
initAntialiasingPinZoom();
initUndoLimit();
initInsecurePixelate();
#ifdef ENABLE_IMGUR
initCopyAndCloseAfterUpload();
initUploadWithoutConfirmation();
Expand Down Expand Up @@ -875,6 +876,20 @@ void GeneralConf::initReverseArrow()
m_reverseArrow, &QCheckBox::clicked, this, &GeneralConf::setReverseArrow);
}

void GeneralConf::initInsecurePixelate()
{
m_insecurePixelate = new QCheckBox(tr("Insecure Pixelate"), this);
m_insecurePixelate->setToolTip(
tr("Draw the pixelation effect in an insecure but more asethetic way."));
m_insecurePixelate->setChecked(ConfigHandler().insecurePixelate());
m_scrollAreaLayout->addWidget(m_insecurePixelate);

connect(m_insecurePixelate,
&QCheckBox::clicked,
this,
&GeneralConf::setInsecurePixelate);
}

void GeneralConf::setSelGeoHideTime(int v)
{
ConfigHandler().setValue("showSelectionGeometryHideTime", v);
Expand Down Expand Up @@ -910,3 +925,8 @@ void GeneralConf::setReverseArrow(bool checked)
{
ConfigHandler().setReverseArrow(checked);
}

void GeneralConf::setInsecurePixelate(bool checked)
{
ConfigHandler().setInsecurePixelate(checked);
}
3 changes: 3 additions & 0 deletions src/config/generalconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private slots:
void setSelGeoHideTime(int v);
void setJpegQuality(int v);
void setReverseArrow(bool checked);
void setInsecurePixelate(bool checked);

private:
const QString chooseFolder(const QString& currentPath = "");
Expand Down Expand Up @@ -99,6 +100,7 @@ private slots:
void initShowSelectionGeometry();
void initJpegQuality();
void initReverseArrow();
void initInsecurePixelate();

void _updateComponents(bool allowEmptySavePath);

Expand Down Expand Up @@ -147,4 +149,5 @@ private slots:
QSpinBox* m_xywhTimeout;
QSpinBox* m_jpegQuality;
QCheckBox* m_reverseArrow;
QCheckBox* m_insecurePixelate;
};
268 changes: 150 additions & 118 deletions src/tools/pixelate/pixelatetool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <array>
#include <random>

#include "confighandler.h"
PixelateTool::PixelateTool(QObject* parent)
: AbstractTwoPointTool(parent)
{}
Expand Down Expand Up @@ -60,133 +61,164 @@ CaptureTool* PixelateTool::copy(QObject* parent)
*/
void PixelateTool::process(QPainter& painter, const QPixmap& pixmap)
{
bool useInsecurePixelate = ConfigHandler().insecurePixelate();

QRect selection = boundingRect().intersected(pixmap.rect());
auto pixelRatio = pixmap.devicePixelRatio();
QRect selectionScaled = QRect(selection.topLeft() * pixelRatio,
selection.bottomRight() * pixelRatio);

// calculate the size of the pixelation effect using the tool size
int width = qMax(
1, static_cast<int>(selection.width() * (0.5 / qMax(1, size() + 1))));
int height = qMax(
1, static_cast<int>(selection.height() * (0.5 / qMax(1, size() + 1))));

QSize effect_size = QSize(width, height);

// the PRNG is only used for visual effects and NOT part of the security
// boundary
std::mt19937 prng(42);

// noise for the sampling process to avoid only sampling from a small
// subset of the fringe
std::normal_distribution<float> sampling_noise(0, 5 * size() + 1);

// additional noise that will be added on top of the effect to avoid
// generating a monochromatic box when the fringe is monochromatic
std::normal_distribution<float> noise(0, 0.1f);

QPoint offset_top(0, selectionScaled.topLeft().y() == 0 ? 0 : -1);
QPoint offset_bottom(
0,
selectionScaled.bottomLeft().y() == pixmap.rect().bottomLeft().y() ? 0
: 1);
QPoint offset_left(selectionScaled.topLeft().x() == 0 ? 0 : -1, 0);
QPoint offset_right(
selectionScaled.topRight().x() == pixmap.rect().topRight().x() ? 0 : 1,
0);

// only values from the fringe will be used to compute the pseudo-pixelation
std::array<QImage, 4> fringe = {
// top fringe
pixmap
.copy(QRect(selectionScaled.topLeft() + offset_top,
selectionScaled.topRight() + offset_top))
.toImage(),
// bottom fringe
pixmap
.copy(QRect(selectionScaled.bottomLeft() + offset_bottom,
selectionScaled.bottomRight() + offset_bottom))
.toImage(),
// left fringe
pixmap
.copy(QRect(selectionScaled.topLeft() + offset_left,
selectionScaled.bottomLeft() + offset_left))
.toImage(),
// right fringe
pixmap
.copy(QRect(selectionScaled.topRight() + offset_right,
selectionScaled.bottomRight() + offset_right))
.toImage()
};

// Image where the pseudo-pixelation is calculated.
// This will later be scaled to cover the selected area.
QImage pixelated = QImage(effect_size, QImage::Format_RGB32);

// For every pixel of the effect, we consider four projections
// to the fringe and sample a pixel from there.
// Then a horizontal and vertical interpolation are calculated.
std::array<std::array<float, 3>, 4> samples;

for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
float n = noise(prng);

// relative horizontal resp. vertical position
float horizontal = x / (float)width;
float vertical = y / (float)height;

for (int i = 0; i < 4; ++i) {
QColor c = fringe[i].pixel(
std::clamp(static_cast<int>(horizontal * fringe[i].width() +
sampling_noise(prng)),
0,
fringe[i].width() - 1),
std::clamp(static_cast<int>(vertical * fringe[i].height() +
sampling_noise(prng)),
0,
fringe[i].height() - 1));
samples[i][0] = c.redF();
samples[i][1] = c.greenF();
samples[i][2] = c.blueF();
}

// weights of the horizontal resp. vertical interpolation
float weight_h = (qMin(x, width - x) / width) -
(qMin(y, height - y) / height) + 0.5;

float weight_v = 1 - weight_h;

// compute the weighted sum of the vertical and horizontal
// interpolations
std::array<int, 3> rgb = { 0, 0, 0 };
for (int i = 0; i < 3; ++i) {
float c =
// horizontal interpolation
weight_h * ((1 - horizontal) * samples[2][i] +
horizontal * samples[3][i])

// vertical interpolation
+ weight_v * ((1 - vertical) * samples[0][i] +
vertical * samples[1][i])

// additional noise
+ n;

rgb[i] = static_cast<int>(0xff * c);
rgb[i] = std::clamp(rgb[i], 0, 0xff);
const auto width =
static_cast<int>(selection.width() * (0.5 / qMax(1, size() + 1)));
const auto height =
static_cast<int>(selection.height() * (0.5 / qMax(1, size() + 1)));
const auto effectSize = QSize(qMax(width, 1), qMax(height, 1));

if (useInsecurePixelate) {
if (size() <= 1) {
auto* blur = new QGraphicsBlurEffect(this);
blur->setBlurRadius(10);
auto* item = new QGraphicsPixmapItem(pixmap.copy(selectionScaled));
item->setGraphicsEffect(blur);

QGraphicsScene scene;
scene.addItem(item);

scene.render(&painter, selection, QRectF());
blur->setBlurRadius(12);
// multiple repeat for make blur effect stronger
scene.render(&painter, selection, QRectF());

} else {
auto pixmapPixelated = pixmap.copy(selectionScaled);
pixmapPixelated = pixmapPixelated.scaled(
effectSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
pixmapPixelated =
pixmapPixelated.scaled(selection.width(), selection.height());
painter.drawImage(selection, pixmapPixelated.toImage());
}
} else {
// the PRNG is only used for visual effects and NOT part of the security
// boundary
std::mt19937 prng(42);

// noise for the sampling process to avoid only sampling from a small
// subset of the fringe
std::normal_distribution<float> sampling_noise(0, 5 * size() + 1);

// additional noise that will be added on top of the effect to avoid
// generating a monochromatic box when the fringe is monochromatic
std::normal_distribution<float> noise(0, 0.1f);

QPoint const offset_top(0, selectionScaled.topLeft().y() == 0 ? 0 : -1);
QPoint const offset_bottom(0,
selectionScaled.bottomLeft().y() ==
pixmap.rect().bottomLeft().y()
? 0
: 1);
QPoint const offset_left(selectionScaled.topLeft().x() == 0 ? 0 : -1,
0);
QPoint const offset_right(
selectionScaled.topRight().x() == pixmap.rect().topRight().x() ? 0
: 1,
0);

// only values from the fringe will be used to compute the
// pseudo-pixelation
std::array<QImage, 4> fringe = {
// top fringe
pixmap
.copy(QRect(selectionScaled.topLeft() + offset_top,
selectionScaled.topRight() + offset_top))
.toImage(),
// bottom fringe
pixmap
.copy(QRect(selectionScaled.bottomLeft() + offset_bottom,
selectionScaled.bottomRight() + offset_bottom))
.toImage(),
// left fringe
pixmap
.copy(QRect(selectionScaled.topLeft() + offset_left,
selectionScaled.bottomLeft() + offset_left))
.toImage(),
// right fringe
pixmap
.copy(QRect(selectionScaled.topRight() + offset_right,
selectionScaled.bottomRight() + offset_right))
.toImage()
};

// Image where the pseudo-pixelation is calculated.
// This will later be scaled to cover the selected area.
QImage pixelated = QImage(effectSize, QImage::Format_RGB32);

// For every pixel of the effect, we consider four projections
// to the fringe and sample a pixel from there.
// Then a horizontal and vertical interpolation are calculated.
std::array<std::array<float, 3>, 4> samples;

for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
float n = noise(prng);

// relative horizontal resp. vertical position
float const horizontal = x / (float)width;
float const vertical = y / (float)height;

for (int i = 0; i < 4; ++i) {
QColor const c = fringe[i].pixel(
std::clamp(
static_cast<int>(horizontal * fringe[i].width() +
sampling_noise(prng)),
0,
fringe[i].width() - 1),
std::clamp(
static_cast<int>(vertical * fringe[i].height() +
sampling_noise(prng)),
0,
fringe[i].height() - 1));
samples[i][0] = c.redF();
samples[i][1] = c.greenF();
samples[i][2] = c.blueF();
}

// weights of the horizontal resp. vertical interpolation
float const weight_h = (qMin(x, width - x) / width) -
(qMin(y, height - y) / height) + 0.5;

float const weight_v = 1 - weight_h;

// compute the weighted sum of the vertical and horizontal
// interpolations
std::array<int, 3> rgb = { 0, 0, 0 };
for (int i = 0; i < 3; ++i) {
float c =
// horizontal interpolation
weight_h * ((1 - horizontal) * samples[2][i] +
horizontal * samples[3][i])

// vertical interpolation
+ weight_v * ((1 - vertical) * samples[0][i] +
vertical * samples[1][i])

// additional noise
+ n;

rgb[i] = static_cast<int>(0xff * c);
rgb[i] = std::clamp(rgb[i], 0, 0xff);
}
QRgb const value = qRgb(rgb[0], rgb[1], rgb[2]);
pixelated.setPixel(x, y, value);
}
QRgb value = qRgb(rgb[0], rgb[1], rgb[2]);
pixelated.setPixel(x, y, value);
}
}

pixelated = pixelated.scaled(selection.width(),
selection.height(),
Qt::IgnoreAspectRatio,
Qt::FastTransformation);
pixelated = pixelated.scaled(selection.width(),
selection.height(),
Qt::IgnoreAspectRatio,
Qt::FastTransformation);

painter.drawImage(selection, pixelated);
painter.drawImage(selection, pixelated);
}
}

void PixelateTool::drawSearchArea(QPainter& painter, const QPixmap& pixmap)
Expand Down
1 change: 1 addition & 0 deletions src/utils/confighandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ static QMap<class QString, QSharedPointer<ValueHandler>>
OPTION("showSelectionGeometryHideTime", LowerBoundedInt ( 0, 3000 )),
OPTION("jpegQuality" , BoundedInt ( 0,100,75 )),
OPTION("reverseArrow" ,Bool ( false )),
OPTION("insecurePixelate" ,Bool ( false )),
};

static QMap<QString, QSharedPointer<KeySequence>> recognizedShortcuts = {
Expand Down
1 change: 1 addition & 0 deletions src/utils/confighandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class ConfigHandler : public QObject
CONFIG_GETTER_SETTER(showSelectionGeometry, setShowSelectionGeometry, int)
CONFIG_GETTER_SETTER(jpegQuality, setJpegQuality, int)
CONFIG_GETTER_SETTER(reverseArrow, setReverseArrow, bool)
CONFIG_GETTER_SETTER(insecurePixelate, setInsecurePixelate, bool)
CONFIG_GETTER_SETTER(showSelectionGeometryHideTime,
showSelectionGeometryHideTime,
int)
Expand Down
Loading