Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[security] replace pixelation by pseudo-pixelation #3765

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
158 changes: 132 additions & 26 deletions src/tools/pixelate/pixelatetool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <QGraphicsScene>
#include <QImage>
#include <QPainter>
#include <random>
#include <array>

PixelateTool::PixelateTool(QObject* parent)
: AbstractTwoPointTool(parent)
Expand Down Expand Up @@ -46,40 +48,144 @@ CaptureTool* PixelateTool::copy(QObject* parent)
return tool;
}

/**
* Since pixelation does not protect the contents of the pixelated area
* (see e.g. /~https://github.com/bishopfox/unredacter),
* _pseudo-pixelation_ is used:
*
* Only colors from the fringe of the selected area are used to generate
* a pixelation-like effect. The interior of the selected area is not used
* as an input at all and hence can not be recovered.
*
*/
void PixelateTool::process(QPainter& painter, const QPixmap& pixmap)
{
QRect selection = boundingRect().intersected(pixmap.rect());
auto pixelRatio = pixmap.devicePixelRatio();
QRect selectionScaled = QRect(selection.topLeft() * pixelRatio,
selection.bottomRight() * pixelRatio);

// If thickness is less than 1, use old blur process
if (size() <= 1) {
auto* blur = new QGraphicsBlurEffect;
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 {
int width =
static_cast<int>(selection.width() * (0.5 / qMax(1, size() + 1)));
int height =
static_cast<int>(selection.height() * (0.5 / qMax(1, size() + 1)));
QSize size = QSize(qMax(width, 1), qMax(height, 1));

QPixmap t = pixmap.copy(selectionScaled);
t = t.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
t = t.scaled(selection.width(), selection.height());
painter.drawImage(selection, t.toImage());

// 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);
}
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);

painter.drawImage(selection, pixelated);
}

void PixelateTool::drawSearchArea(QPainter& painter, const QPixmap& pixmap)
Expand Down