Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_qimage_pyramid.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <limits>
10#include <QPainter>
11#include <kis_debug.h>
12
13#define MIPMAP_SIZE_THRESHOLD 512
14#define MAX_MIPMAP_SCALE 8.0
15
16#define QPAINTER_WORKAROUND_BORDER 1
17
18
19KisQImagePyramid::KisQImagePyramid(const QImage &baseImage, bool useSmoothingForEnlarging)
20{
21 KIS_SAFE_ASSERT_RECOVER_RETURN(!baseImage.isNull());
22
23 m_originalSize = baseImage.size();
24
25
26 qreal scale = MAX_MIPMAP_SCALE;
27
28 while (scale > 1.0) {
29 QSize scaledSize = m_originalSize * scale;
30
31 if (scaledSize.width() <= MIPMAP_SIZE_THRESHOLD ||
32 scaledSize.height() <= MIPMAP_SIZE_THRESHOLD) {
33
34 if (m_levels.isEmpty()) {
35 m_baseScale = scale;
36 }
37
38 if (useSmoothingForEnlarging) {
39 appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
40 } else {
41 appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::FastTransformation));
42 }
43 }
44
45 scale *= 0.5;
46 }
47
48 if (m_levels.isEmpty()) {
49 m_baseScale = 1.0;
50 }
51 appendPyramidLevel(baseImage);
52
53 scale = 0.5;
54 while (true) {
55 QSize scaledSize = m_originalSize * scale;
56
57 if (scaledSize.width() == 0 ||
58 scaledSize.height() == 0) break;
59
60 appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
61
62 scale *= 0.5;
63 }
64}
65
69
70int KisQImagePyramid::findNearestLevel(qreal scale, qreal *baseScale) const
71{
72 const qreal scale_epsilon = 1e-6;
73
74 qreal levelScale = m_baseScale;
75 int level = 0;
76 int lastLevel = m_levels.size() - 1;
77
78
79 while ((0.5 * levelScale > scale ||
80 qAbs(0.5 * levelScale - scale) < scale_epsilon) &&
81 level < lastLevel) {
82
83 levelScale *= 0.5;
84 level++;
85 }
86
87 *baseScale = levelScale;
88 return level;
89}
90
91inline QRect roundRect(const QRectF &rc)
92{
102 QRectF rect(rc);
103
104 KIS_SAFE_ASSERT_RECOVER_NOOP(rect.x() > -0.000001);
105 KIS_SAFE_ASSERT_RECOVER_NOOP(rect.y() > -0.000001);
106
107 if (rect.x() < 0.000001) {
108 rect.setLeft(0.0);
109 }
110
111 if (rect.y() < 0.000001) {
112 rect.setTop(0.0);
113 }
114
115 qreal w_rounded = qRound(rect.width());
116 qreal h_rounded = qRound(rect.height());
117
118 //Take care of the float precision errors
119 if (qAbs(rect.width() - w_rounded) < 0.000001) {
120 rect.setWidth(w_rounded);
121 }
122
123 if (qAbs(rect.height() - h_rounded) < 0.000001) {
124 rect.setHeight(h_rounded);
125 }
126
127
128 return rect.toAlignedRect();
129}
130
131QTransform baseBrushTransform(KisDabShape const& shape,
132 qreal subPixelX, qreal subPixelY,
133 const QRectF &baseBounds)
134{
135 QTransform transform;
136 transform.scale(shape.scaleX(), shape.scaleY());
137
138 if (!qFuzzyCompare(shape.rotation(), 0) && !qIsNaN(shape.rotation())) {
139 transform = transform * QTransform().rotateRadians(shape.rotation());
140 QRectF rotatedBounds = transform.mapRect(baseBounds);
141 transform = transform * QTransform::fromTranslate(-rotatedBounds.x(), -rotatedBounds.y());
142 }
143
144 return transform * QTransform::fromTranslate(subPixelX, subPixelY);
145}
146
148 qreal subPixelX, qreal subPixelY,
149 const QSize &originalSize,
150 QTransform *outputTransform, QSize *outputSize)
151{
152 calculateParams(shape,
153 subPixelX, subPixelY,
154 originalSize, 1.0, originalSize,
155 outputTransform, outputSize);
156}
157
159 qreal subPixelX, qreal subPixelY,
160 const QSize &originalSize,
161 qreal baseScale, const QSize &baseSize,
162 QTransform *outputTransform, QSize *outputSize)
163{
164 Q_UNUSED(baseScale);
165
166 QRectF originalBounds = QRectF(QPointF(), originalSize);
167 QTransform originalTransform = baseBrushTransform(shape, subPixelX, subPixelY, originalBounds);
168
169 qreal realBaseScaleX = qreal(baseSize.width()) / originalSize.width();
170 qreal realBaseScaleY = qreal(baseSize.height()) / originalSize.height();
171 qreal scaleX = shape.scaleX() / realBaseScaleX;
172 qreal scaleY = shape.scaleY() / realBaseScaleY;
173 shape = KisDabShape(scaleX, scaleY/scaleX, shape.rotation());
174
175 QRectF baseBounds = QRectF(QPointF(), baseSize);
176 QTransform transform = baseBrushTransform(shape, subPixelX, subPixelY, baseBounds);
177 QRectF mappedRect = originalTransform.mapRect(originalBounds);
178
179 // Set up a 0,0,1,1 size and identity transform in case the transform fails to
180 // produce a usable result.
181 int width = 1;
182 int height = 1;
183 *outputTransform = QTransform();
184
185 if (mappedRect.isValid()) {
186 QRect expectedDstRect = roundRect(mappedRect);
187
188#if 0 // Only enable when debugging; users shouldn't see this warning
189 {
190 QRect testingRect = roundRect(transform.mapRect(baseBounds));
191 if (testingRect != expectedDstRect) {
192 warnKrita << "WARNING: expected and real dab rects do not coincide!";
193 warnKrita << " expected rect:" << expectedDstRect;
194 warnKrita << " real rect: " << testingRect;
195 }
196 }
197#endif
198 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedDstRect.x() >= 0);
199 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedDstRect.y() >= 0);
200
201 width = expectedDstRect.x() + expectedDstRect.width();
202 height = expectedDstRect.y() + expectedDstRect.height();
203
204 // we should not return invalid image, so adjust the image to be
205 // at least 1 px in size.
206 width = qMax(1, width);
207 height = qMax(1, height);
208 }
209 else {
210#if 0 // Only enable when debugging; users shouldn't see this warning
211 qWarning() << "Brush transform generated an invalid rectangle!"
212 << ppVar(shape.scaleX()) << ppVar(shape.scaleY()) << ppVar(shape.rotation())
213 << ppVar(subPixelX) << ppVar(subPixelY)
214 << ppVar(originalSize)
215 << ppVar(baseScale)
216 << ppVar(baseSize)
217 << ppVar(baseBounds)
218 << ppVar(mappedRect);
219#endif
220 }
221
222 *outputTransform = transform;
223 *outputSize = QSize(width, height);
224}
225
226QSize KisQImagePyramid::imageSize(const QSize &originalSize,
227 KisDabShape const& shape,
228 qreal subPixelX, qreal subPixelY)
229{
230 QTransform transform;
231 QSize dstSize;
232
233 calculateParams(shape, subPixelX, subPixelY,
234 originalSize,
235 &transform, &dstSize);
236
237 return dstSize;
238}
239
240QSizeF KisQImagePyramid::characteristicSize(const QSize &originalSize,
241 KisDabShape const& shape)
242{
243 QRectF originalRect(QPointF(), originalSize);
244 QTransform transform = baseBrushTransform(shape,
245 0.0, 0.0,
246 originalRect);
247
248 return transform.mapRect(originalRect).size();
249}
250
252{
264QSize levelSize = image.size();
265 QImage tmp = image.convertToFormat(QImage::Format_ARGB32);
268 image.width() + 2 * QPAINTER_WORKAROUND_BORDER,
269 image.height() + 2 * QPAINTER_WORKAROUND_BORDER);
270 m_levels.append(PyramidLevel(tmp, levelSize));
271}
272
274 qreal subPixelX, qreal subPixelY) const
275{
276 if (m_levels.isEmpty()) return QImage();
277
278 qreal baseScale = -1.0;
279 int level = findNearestLevel(shape.scale(), &baseScale);
280
281 const QImage &srcImage = m_levels[level].image;
282
283 QTransform transform;
284 QSize dstSize;
285
286 calculateParams(shape, subPixelX, subPixelY,
287 m_originalSize, baseScale, m_levels[level].size,
288 &transform, &dstSize);
289
290 if (transform.isIdentity() &&
291 srcImage.format() == QImage::Format_ARGB32) {
292
293 return srcImage.copy(QPAINTER_WORKAROUND_BORDER,
295 srcImage.width() - 2 * QPAINTER_WORKAROUND_BORDER,
296 srcImage.height() - 2 * QPAINTER_WORKAROUND_BORDER);
297 }
298
299 QImage dstImage(dstSize, QImage::Format_ARGB32);
300 dstImage.fill(0);
301
302
311 while (transform.type() == QTransform::TxTranslate) {
312 const qreal scale = transform.m11();
313 const qreal fakeScale = scale - 10 * std::numeric_limits<qreal>::epsilon();
314 transform *= QTransform::fromScale(fakeScale, fakeScale);
315 }
316
317 QPainter gc(&dstImage);
318 gc.setTransform(
319 QTransform::fromTranslate(-QPAINTER_WORKAROUND_BORDER,
320 -QPAINTER_WORKAROUND_BORDER) * transform);
321 gc.setRenderHints(QPainter::SmoothPixmapTransform);
322 gc.drawImage(QPointF(), srcImage);
323 gc.end();
324
325 return dstImage;
326}
327
328QImage KisQImagePyramid::getClosest(QTransform transform, qreal *scale) const
329{
330 if (m_levels.isEmpty()) return QImage();
331
332 // Estimate scale
333 QSizeF transformedUnitSquare = transform.mapRect(QRectF(0, 0, 1, 1)).size();
334 qreal x = qAbs(transformedUnitSquare.width());
335 qreal y = qAbs(transformedUnitSquare.height());
336 qreal estimatedScale = (x > y) ? transformedUnitSquare.width() : transformedUnitSquare.height();
337
338 int level = findNearestLevel(estimatedScale, scale);
339 return m_levels[level].image;
340}
341
342QImage KisQImagePyramid::getClosestWithoutWorkaroundBorder(QTransform transform, qreal *scale) const
343{
344 QImage image = getClosest(transform, scale);
345 return image.copy(QPAINTER_WORKAROUND_BORDER,
347 image.width() - 2 * QPAINTER_WORKAROUND_BORDER,
348 image.height() - 2 * QPAINTER_WORKAROUND_BORDER);
349}
qreal scaleY() const
qreal scale() const
qreal scaleX() const
qreal rotation() const
KisQImagePyramid()=default
void appendPyramidLevel(const QImage &image)
QImage getClosest(QTransform transform, qreal *scale) const
static void calculateParams(KisDabShape const &shape, qreal subPixelX, qreal subPixelY, const QSize &originalSize, QTransform *outputTransform, QSize *outputSize)
static QSize imageSize(const QSize &originalSize, KisDabShape const &, qreal subPixelX, qreal subPixelY)
static QSizeF characteristicSize(const QSize &originalSize, KisDabShape const &)
QVector< PyramidLevel > m_levels
int findNearestLevel(qreal scale, qreal *baseScale) const
QImage createImage(KisDabShape const &, qreal subPixelX, qreal subPixelY) const
QImage getClosestWithoutWorkaroundBorder(QTransform transform, qreal *scale) const
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
#define MIPMAP_SIZE_THRESHOLD
QRect roundRect(const QRectF &rc)
#define QPAINTER_WORKAROUND_BORDER
#define MAX_MIPMAP_SCALE
QTransform baseBrushTransform(KisDabShape const &shape, qreal subPixelX, qreal subPixelY, const QRectF &baseBounds)