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 return rect.toAlignedRect();
116}
117
118QTransform baseBrushTransform(KisDabShape const& shape,
119 qreal subPixelX, qreal subPixelY,
120 const QRectF &baseBounds)
121{
122 QTransform transform;
123 transform.scale(shape.scaleX(), shape.scaleY());
124
125 if (!qFuzzyCompare(shape.rotation(), 0) && !qIsNaN(shape.rotation())) {
126 transform = transform * QTransform().rotateRadians(shape.rotation());
127 QRectF rotatedBounds = transform.mapRect(baseBounds);
128 transform = transform * QTransform::fromTranslate(-rotatedBounds.x(), -rotatedBounds.y());
129 }
130
131 return transform * QTransform::fromTranslate(subPixelX, subPixelY);
132}
133
135 qreal subPixelX, qreal subPixelY,
136 const QSize &originalSize,
137 QTransform *outputTransform, QSize *outputSize)
138{
139 calculateParams(shape,
140 subPixelX, subPixelY,
141 originalSize, 1.0, originalSize,
142 outputTransform, outputSize);
143}
144
146 qreal subPixelX, qreal subPixelY,
147 const QSize &originalSize,
148 qreal baseScale, const QSize &baseSize,
149 QTransform *outputTransform, QSize *outputSize)
150{
151 Q_UNUSED(baseScale);
152
153 QRectF originalBounds = QRectF(QPointF(), originalSize);
154 QTransform originalTransform = baseBrushTransform(shape, subPixelX, subPixelY, originalBounds);
155
156 qreal realBaseScaleX = qreal(baseSize.width()) / originalSize.width();
157 qreal realBaseScaleY = qreal(baseSize.height()) / originalSize.height();
158 qreal scaleX = shape.scaleX() / realBaseScaleX;
159 qreal scaleY = shape.scaleY() / realBaseScaleY;
160 shape = KisDabShape(scaleX, scaleY/scaleX, shape.rotation());
161
162 QRectF baseBounds = QRectF(QPointF(), baseSize);
163 QTransform transform = baseBrushTransform(shape, subPixelX, subPixelY, baseBounds);
164 QRectF mappedRect = originalTransform.mapRect(originalBounds);
165
166 // Set up a 0,0,1,1 size and identity transform in case the transform fails to
167 // produce a usable result.
168 int width = 1;
169 int height = 1;
170 *outputTransform = QTransform();
171
172 if (mappedRect.isValid()) {
173 QRect expectedDstRect = roundRect(mappedRect);
174
175#if 0 // Only enable when debugging; users shouldn't see this warning
176 {
177 QRect testingRect = roundRect(transform.mapRect(baseBounds));
178 if (testingRect != expectedDstRect) {
179 warnKrita << "WARNING: expected and real dab rects do not coincide!";
180 warnKrita << " expected rect:" << expectedDstRect;
181 warnKrita << " real rect: " << testingRect;
182 }
183 }
184#endif
185 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedDstRect.x() >= 0);
186 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedDstRect.y() >= 0);
187
188 width = expectedDstRect.x() + expectedDstRect.width();
189 height = expectedDstRect.y() + expectedDstRect.height();
190
191 // we should not return invalid image, so adjust the image to be
192 // at least 1 px in size.
193 width = qMax(1, width);
194 height = qMax(1, height);
195 }
196 else {
197#if 0 // Only enable when debugging; users shouldn't see this warning
198 qWarning() << "Brush transform generated an invalid rectangle!"
199 << ppVar(shape.scaleX()) << ppVar(shape.scaleY()) << ppVar(shape.rotation())
200 << ppVar(subPixelX) << ppVar(subPixelY)
201 << ppVar(originalSize)
202 << ppVar(baseScale)
203 << ppVar(baseSize)
204 << ppVar(baseBounds)
205 << ppVar(mappedRect);
206#endif
207 }
208
209 *outputTransform = transform;
210 *outputSize = QSize(width, height);
211}
212
213QSize KisQImagePyramid::imageSize(const QSize &originalSize,
214 KisDabShape const& shape,
215 qreal subPixelX, qreal subPixelY)
216{
217 QTransform transform;
218 QSize dstSize;
219
220 calculateParams(shape, subPixelX, subPixelY,
221 originalSize,
222 &transform, &dstSize);
223
224 return dstSize;
225}
226
227QSizeF KisQImagePyramid::characteristicSize(const QSize &originalSize,
228 KisDabShape const& shape)
229{
230 QRectF originalRect(QPointF(), originalSize);
231 QTransform transform = baseBrushTransform(shape,
232 0.0, 0.0,
233 originalRect);
234
235 return transform.mapRect(originalRect).size();
236}
237
239{
251QSize levelSize = image.size();
252 QImage tmp = image.convertToFormat(QImage::Format_ARGB32);
255 image.width() + 2 * QPAINTER_WORKAROUND_BORDER,
256 image.height() + 2 * QPAINTER_WORKAROUND_BORDER);
257 m_levels.append(PyramidLevel(tmp, levelSize));
258}
259
261 qreal subPixelX, qreal subPixelY) const
262{
263 if (m_levels.isEmpty()) return QImage();
264
265 qreal baseScale = -1.0;
266 int level = findNearestLevel(shape.scale(), &baseScale);
267
268 const QImage &srcImage = m_levels[level].image;
269
270 QTransform transform;
271 QSize dstSize;
272
273 calculateParams(shape, subPixelX, subPixelY,
274 m_originalSize, baseScale, m_levels[level].size,
275 &transform, &dstSize);
276
277 if (transform.isIdentity() &&
278 srcImage.format() == QImage::Format_ARGB32) {
279
280 return srcImage.copy(QPAINTER_WORKAROUND_BORDER,
282 srcImage.width() - 2 * QPAINTER_WORKAROUND_BORDER,
283 srcImage.height() - 2 * QPAINTER_WORKAROUND_BORDER);
284 }
285
286 QImage dstImage(dstSize, QImage::Format_ARGB32);
287 dstImage.fill(0);
288
289
298 while (transform.type() == QTransform::TxTranslate) {
299 const qreal scale = transform.m11();
300 const qreal fakeScale = scale - 10 * std::numeric_limits<qreal>::epsilon();
301 transform *= QTransform::fromScale(fakeScale, fakeScale);
302 }
303
304 QPainter gc(&dstImage);
305 gc.setTransform(
306 QTransform::fromTranslate(-QPAINTER_WORKAROUND_BORDER,
307 -QPAINTER_WORKAROUND_BORDER) * transform);
308 gc.setRenderHints(QPainter::SmoothPixmapTransform);
309 gc.drawImage(QPointF(), srcImage);
310 gc.end();
311
312 return dstImage;
313}
314
315QImage KisQImagePyramid::getClosest(QTransform transform, qreal *scale) const
316{
317 if (m_levels.isEmpty()) return QImage();
318
319 // Estimate scale
320 QSizeF transformedUnitSquare = transform.mapRect(QRectF(0, 0, 1, 1)).size();
321 qreal x = qAbs(transformedUnitSquare.width());
322 qreal y = qAbs(transformedUnitSquare.height());
323 qreal estimatedScale = (x > y) ? transformedUnitSquare.width() : transformedUnitSquare.height();
324
325 int level = findNearestLevel(estimatedScale, scale);
326 return m_levels[level].image;
327}
328
329QImage KisQImagePyramid::getClosestWithoutWorkaroundBorder(QTransform transform, qreal *scale) const
330{
331 QImage image = getClosest(transform, scale);
332 return image.copy(QPAINTER_WORKAROUND_BORDER,
334 image.width() - 2 * QPAINTER_WORKAROUND_BORDER,
335 image.height() - 2 * QPAINTER_WORKAROUND_BORDER);
336}
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)