Skip to content

Commit 533a1b7

Browse files
authored
[security] replace pixelation by pseudo-pixelation (flameshot-org#3765)
* replaced pixelation by pseudo-pixelation * pseudo-pixelation: sample from outside of the area if possible * pseudo-pixelation: include <array>
1 parent 14619b1 commit 533a1b7

File tree

1 file changed

+132
-26
lines changed

1 file changed

+132
-26
lines changed

src/tools/pixelate/pixelatetool.cpp

Lines changed: 132 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <QGraphicsScene>
99
#include <QImage>
1010
#include <QPainter>
11+
#include <random>
12+
#include <array>
1113

1214
PixelateTool::PixelateTool(QObject* parent)
1315
: AbstractTwoPointTool(parent)
@@ -51,40 +53,144 @@ CaptureTool* PixelateTool::copy(QObject* parent)
5153
return tool;
5254
}
5355

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

61-
// If thickness is less than 1, use old blur process
62-
if (size() <= 1) {
63-
auto* blur = new QGraphicsBlurEffect;
64-
blur->setBlurRadius(10);
65-
auto* item = new QGraphicsPixmapItem(pixmap.copy(selectionScaled));
66-
item->setGraphicsEffect(blur);
67-
68-
QGraphicsScene scene;
69-
scene.addItem(item);
70-
71-
scene.render(&painter, selection, QRectF());
72-
blur->setBlurRadius(12);
73-
// multiple repeat for make blur effect stronger
74-
scene.render(&painter, selection, QRectF());
75-
76-
} else {
77-
int width =
78-
static_cast<int>(selection.width() * (0.5 / qMax(1, size() + 1)));
79-
int height =
80-
static_cast<int>(selection.height() * (0.5 / qMax(1, size() + 1)));
81-
QSize size = QSize(qMax(width, 1), qMax(height, 1));
82-
83-
QPixmap t = pixmap.copy(selectionScaled);
84-
t = t.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
85-
t = t.scaled(selection.width(), selection.height());
86-
painter.drawImage(selection, t.toImage());
73+
74+
// calculate the size of the pixelation effect using the tool size
75+
int width = qMax(1,
76+
static_cast<int>(selection.width() * (0.5 / qMax(1, size() + 1))));
77+
int height = qMax(1,
78+
static_cast<int>(selection.height() * (0.5 / qMax(1, size() + 1))));
79+
80+
QSize effect_size = QSize(width, height);
81+
82+
83+
// the PRNG is only used for visual effects and NOT part of the security
84+
// boundary
85+
std::mt19937 prng(42);
86+
87+
// noise for the sampling process to avoid only sampling from a small
88+
// subset of the fringe
89+
std::normal_distribution<float> sampling_noise(0, 5 * size() + 1);
90+
91+
// additional noise that will be added on top of the effect to avoid
92+
// generating a monochromatic box when the fringe is monochromatic
93+
std::normal_distribution<float> noise(0, 0.1f);
94+
95+
96+
QPoint offset_top
97+
(0, selectionScaled.topLeft().y() == 0 ? 0 : -1);
98+
QPoint offset_bottom
99+
(0, selectionScaled.bottomLeft().y() == pixmap.rect().bottomLeft().y() ? 0 : 1);
100+
QPoint offset_left
101+
(selectionScaled.topLeft().x() == 0 ? 0 : -1,0);
102+
QPoint offset_right
103+
(selectionScaled.topRight().x() == pixmap.rect().topRight().x() ? 0 : 1,0);
104+
105+
// only values from the fringe will be used to compute the pseudo-pixelation
106+
std::array<QImage, 4> fringe = {
107+
// top fringe
108+
pixmap.copy(QRect(selectionScaled.topLeft() + offset_top,
109+
selectionScaled.topRight() + offset_top))
110+
.toImage(),
111+
// bottom fringe
112+
pixmap.copy(QRect(selectionScaled.bottomLeft() + offset_bottom,
113+
selectionScaled.bottomRight() + offset_bottom))
114+
.toImage(),
115+
// left fringe
116+
pixmap.copy(QRect(selectionScaled.topLeft() + offset_left,
117+
selectionScaled.bottomLeft() + offset_left))
118+
.toImage(),
119+
// right fringe
120+
pixmap.copy(QRect(selectionScaled.topRight() + offset_right,
121+
selectionScaled.bottomRight() + offset_right))
122+
.toImage()
123+
};
124+
125+
126+
// Image where the pseudo-pixelation is calculated.
127+
// This will later be scaled to cover the selected area.
128+
QImage pixelated = QImage(effect_size, QImage::Format_RGB32);
129+
130+
131+
// For every pixel of the effect, we consider four projections
132+
// to the fringe and sample a pixel from there.
133+
// Then a horizontal and vertical interpolation are calculated.
134+
std::array<std::array<float, 3>, 4> samples;
135+
136+
for (int x = 0; x < width; ++x) {
137+
for (int y = 0; y < height; ++y) {
138+
float n = noise(prng);
139+
140+
// relative horizontal resp. vertical position
141+
float horizontal = x / (float) width;
142+
float vertical = y / (float) height;
143+
144+
for (int i = 0; i < 4; ++i) {
145+
QColor c = fringe[i].pixel(
146+
std::clamp(
147+
static_cast<int>(
148+
horizontal * fringe[i].width()
149+
+ sampling_noise(prng)),
150+
0, fringe[i].width()-1),
151+
std::clamp(
152+
static_cast<int>(
153+
vertical * fringe[i].height()
154+
+ sampling_noise(prng)),
155+
0, fringe[i].height()-1));
156+
samples[i][0] = c.redF();
157+
samples[i][1] = c.greenF();
158+
samples[i][2] = c.blueF();
159+
}
160+
161+
// weights of the horizontal resp. vertical interpolation
162+
float weight_h = (qMin(x, width - x) / width)
163+
- (qMin(y, height - y) / height)
164+
+ 0.5;
165+
166+
float weight_v = 1 - weight_h;
167+
168+
// compute the weighted sum of the vertical and horizontal
169+
// interpolations
170+
std::array<int, 3> rgb = {0, 0, 0};
171+
for (int i = 0; i < 3; ++i) {
172+
float c =
173+
// horizontal interpolation
174+
weight_h * ((1-horizontal) * samples[2][i] + horizontal * samples[3][i])
175+
176+
// vertical interpolation
177+
+ weight_v * ((1-vertical) * samples[0][i] + vertical * samples[1][i])
178+
179+
// additional noise
180+
+ n;
181+
182+
rgb[i] = static_cast<int>(0xff * c);
183+
rgb[i] = std::clamp(rgb[i], 0, 0xff);
184+
}
185+
QRgb value = qRgb(rgb[0], rgb[1], rgb[2]);
186+
pixelated.setPixel(x,y, value);
187+
}
87188
}
189+
190+
pixelated = pixelated.scaled(selection.width(), selection.height(),
191+
Qt::IgnoreAspectRatio, Qt::FastTransformation);
192+
193+
painter.drawImage(selection, pixelated);
88194
}
89195

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

0 commit comments

Comments
 (0)