1 /*
2 * Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "kis_qimage_pyramid.h"
20
21 #include <limits>
22 #include <QPainter>
23 #include <kis_debug.h>
24
25 #define MIPMAP_SIZE_THRESHOLD 512
26 #define MAX_MIPMAP_SCALE 8.0
27
28 #define QPAINTER_WORKAROUND_BORDER 1
29
30
KisQImagePyramid(const QImage & baseImage,bool useSmoothingForEnlarging)31 KisQImagePyramid::KisQImagePyramid(const QImage &baseImage, bool useSmoothingForEnlarging)
32 {
33 KIS_SAFE_ASSERT_RECOVER_RETURN(!baseImage.isNull());
34
35 m_originalSize = baseImage.size();
36
37
38 qreal scale = MAX_MIPMAP_SCALE;
39
40 while (scale > 1.0) {
41 QSize scaledSize = m_originalSize * scale;
42
43 if (scaledSize.width() <= MIPMAP_SIZE_THRESHOLD ||
44 scaledSize.height() <= MIPMAP_SIZE_THRESHOLD) {
45
46 if (m_levels.isEmpty()) {
47 m_baseScale = scale;
48 }
49
50 if (useSmoothingForEnlarging) {
51 appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
52 } else {
53 appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::FastTransformation));
54 }
55 }
56
57 scale *= 0.5;
58 }
59
60 if (m_levels.isEmpty()) {
61 m_baseScale = 1.0;
62 }
63 appendPyramidLevel(baseImage);
64
65 scale = 0.5;
66 while (true) {
67 QSize scaledSize = m_originalSize * scale;
68
69 if (scaledSize.width() == 0 ||
70 scaledSize.height() == 0) break;
71
72 appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
73
74 scale *= 0.5;
75 }
76 }
77
~KisQImagePyramid()78 KisQImagePyramid::~KisQImagePyramid()
79 {
80 }
81
findNearestLevel(qreal scale,qreal * baseScale) const82 int KisQImagePyramid::findNearestLevel(qreal scale, qreal *baseScale) const
83 {
84 const qreal scale_epsilon = 1e-6;
85
86 qreal levelScale = m_baseScale;
87 int level = 0;
88 int lastLevel = m_levels.size() - 1;
89
90
91 while ((0.5 * levelScale > scale ||
92 qAbs(0.5 * levelScale - scale) < scale_epsilon) &&
93 level < lastLevel) {
94
95 levelScale *= 0.5;
96 level++;
97 }
98
99 *baseScale = levelScale;
100 return level;
101 }
102
roundRect(const QRectF & rc)103 inline QRect roundRect(const QRectF &rc)
104 {
105 /**
106 * This is an analog of toAlignedRect() with the only difference
107 * that it ensures the rect position will never be below zero.
108 *
109 * Warning: be *very* careful with using bottom()/right() values
110 * of a pure QRect (we don't use it here for the dangers
111 * it can lead to).
112 */
113
114 QRectF rect(rc);
115
116 KIS_SAFE_ASSERT_RECOVER_NOOP(rect.x() > -0.000001);
117 KIS_SAFE_ASSERT_RECOVER_NOOP(rect.y() > -0.000001);
118
119 if (rect.x() < 0.000001) {
120 rect.setLeft(0.0);
121 }
122
123 if (rect.y() < 0.000001) {
124 rect.setTop(0.0);
125 }
126
127 return rect.toAlignedRect();
128 }
129
baseBrushTransform(KisDabShape const & shape,qreal subPixelX,qreal subPixelY,const QRectF & baseBounds)130 QTransform baseBrushTransform(KisDabShape const& shape,
131 qreal subPixelX, qreal subPixelY,
132 const QRectF &baseBounds)
133 {
134 QTransform transform;
135 transform.scale(shape.scaleX(), shape.scaleY());
136
137 if (!qFuzzyCompare(shape.rotation(), 0) && !qIsNaN(shape.rotation())) {
138 transform = transform * QTransform().rotateRadians(shape.rotation());
139 QRectF rotatedBounds = transform.mapRect(baseBounds);
140 transform = transform * QTransform::fromTranslate(-rotatedBounds.x(), -rotatedBounds.y());
141 }
142
143 return transform * QTransform::fromTranslate(subPixelX, subPixelY);
144 }
145
calculateParams(KisDabShape const & shape,qreal subPixelX,qreal subPixelY,const QSize & originalSize,QTransform * outputTransform,QSize * outputSize)146 void KisQImagePyramid::calculateParams(KisDabShape const& shape,
147 qreal subPixelX, qreal subPixelY,
148 const QSize &originalSize,
149 QTransform *outputTransform, QSize *outputSize)
150 {
151 calculateParams(shape,
152 subPixelX, subPixelY,
153 originalSize, 1.0, originalSize,
154 outputTransform, outputSize);
155 }
156
calculateParams(KisDabShape shape,qreal subPixelX,qreal subPixelY,const QSize & originalSize,qreal baseScale,const QSize & baseSize,QTransform * outputTransform,QSize * outputSize)157 void KisQImagePyramid::calculateParams(KisDabShape shape,
158 qreal subPixelX, qreal subPixelY,
159 const QSize &originalSize,
160 qreal baseScale, const QSize &baseSize,
161 QTransform *outputTransform, QSize *outputSize)
162 {
163 Q_UNUSED(baseScale);
164
165 QRectF originalBounds = QRectF(QPointF(), originalSize);
166 QTransform originalTransform = baseBrushTransform(shape, subPixelX, subPixelY, originalBounds);
167
168 qreal realBaseScaleX = qreal(baseSize.width()) / originalSize.width();
169 qreal realBaseScaleY = qreal(baseSize.height()) / originalSize.height();
170 qreal scaleX = shape.scaleX() / realBaseScaleX;
171 qreal scaleY = shape.scaleY() / realBaseScaleY;
172 shape = KisDabShape(scaleX, scaleY/scaleX, shape.rotation());
173
174 QRectF baseBounds = QRectF(QPointF(), baseSize);
175 QTransform transform = baseBrushTransform(shape, subPixelX, subPixelY, baseBounds);
176 QRectF mappedRect = originalTransform.mapRect(originalBounds);
177
178 // Set up a 0,0,1,1 size and identity transform in case the transform fails to
179 // produce a usable result.
180 int width = 1;
181 int height = 1;
182 *outputTransform = QTransform();
183
184 if (mappedRect.isValid()) {
185 QRect expectedDstRect = roundRect(mappedRect);
186
187 #if 0 // Only enable when debugging; users shouldn't see this warning
188 {
189 QRect testingRect = roundRect(transform.mapRect(baseBounds));
190 if (testingRect != expectedDstRect) {
191 warnKrita << "WARNING: expected and real dab rects do not coincide!";
192 warnKrita << " expected rect:" << expectedDstRect;
193 warnKrita << " real rect: " << testingRect;
194 }
195 }
196 #endif
197 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedDstRect.x() >= 0);
198 KIS_SAFE_ASSERT_RECOVER_NOOP(expectedDstRect.y() >= 0);
199
200 width = expectedDstRect.x() + expectedDstRect.width();
201 height = expectedDstRect.y() + expectedDstRect.height();
202
203 // we should not return invalid image, so adjust the image to be
204 // at least 1 px in size.
205 width = qMax(1, width);
206 height = qMax(1, height);
207 }
208 else {
209 qWarning() << "Brush transform generated an invalid rectangle!"
210 << ppVar(shape.scaleX()) << ppVar(shape.scaleY()) << ppVar(shape.rotation())
211 << ppVar(subPixelX) << ppVar(subPixelY)
212 << ppVar(originalSize)
213 << ppVar(baseScale)
214 << ppVar(baseSize)
215 << ppVar(baseBounds)
216 << ppVar(mappedRect);
217 }
218
219 *outputTransform = transform;
220 *outputSize = QSize(width, height);
221 }
222
imageSize(const QSize & originalSize,KisDabShape const & shape,qreal subPixelX,qreal subPixelY)223 QSize KisQImagePyramid::imageSize(const QSize &originalSize,
224 KisDabShape const& shape,
225 qreal subPixelX, qreal subPixelY)
226 {
227 QTransform transform;
228 QSize dstSize;
229
230 calculateParams(shape, subPixelX, subPixelY,
231 originalSize,
232 &transform, &dstSize);
233
234 return dstSize;
235 }
236
characteristicSize(const QSize & originalSize,KisDabShape const & shape)237 QSizeF KisQImagePyramid::characteristicSize(const QSize &originalSize,
238 KisDabShape const& shape)
239 {
240 QRectF originalRect(QPointF(), originalSize);
241 QTransform transform = baseBrushTransform(shape,
242 0.0, 0.0,
243 originalRect);
244
245 return transform.mapRect(originalRect).size();
246 }
247
appendPyramidLevel(const QImage & image)248 void KisQImagePyramid::appendPyramidLevel(const QImage &image)
249 {
250 /**
251 * QPainter has a bug: when doing a transformation it decides that
252 * all the pixels outside of the image (source rect) are equal to
253 * the border pixels (CLAMP in terms of openGL). This means that
254 * there will be no smooth scaling on the border of the image when
255 * it is rotated. To workaround this bug we need to add one pixel
256 * wide border to the image, so that it transforms smoothly.
257 *
258 * See a unittest in: KisGbrBrushTest::testQPainterTransformationBorder
259 */
260
261 QSize levelSize = image.size();
262 QImage tmp = image.convertToFormat(QImage::Format_ARGB32);
263 tmp = tmp.copy(-QPAINTER_WORKAROUND_BORDER,
264 -QPAINTER_WORKAROUND_BORDER,
265 image.width() + 2 * QPAINTER_WORKAROUND_BORDER,
266 image.height() + 2 * QPAINTER_WORKAROUND_BORDER);
267 m_levels.append(PyramidLevel(tmp, levelSize));
268 }
269
createImage(KisDabShape const & shape,qreal subPixelX,qreal subPixelY) const270 QImage KisQImagePyramid::createImage(KisDabShape const& shape,
271 qreal subPixelX, qreal subPixelY) const
272 {
273 if (m_levels.isEmpty()) return QImage();
274
275 qreal baseScale = -1.0;
276 int level = findNearestLevel(shape.scale(), &baseScale);
277
278 const QImage &srcImage = m_levels[level].image;
279
280 QTransform transform;
281 QSize dstSize;
282
283 calculateParams(shape, subPixelX, subPixelY,
284 m_originalSize, baseScale, m_levels[level].size,
285 &transform, &dstSize);
286
287 if (transform.isIdentity() &&
288 srcImage.format() == QImage::Format_ARGB32) {
289
290 return srcImage.copy(QPAINTER_WORKAROUND_BORDER,
291 QPAINTER_WORKAROUND_BORDER,
292 srcImage.width() - 2 * QPAINTER_WORKAROUND_BORDER,
293 srcImage.height() - 2 * QPAINTER_WORKAROUND_BORDER);
294 }
295
296 QImage dstImage(dstSize, QImage::Format_ARGB32);
297 dstImage.fill(0);
298
299
300 /**
301 * QPainter has one more bug: when a QTransform is TxTranslate, it
302 * does wrong sampling (probably, Nearest Neighbour) even though
303 * we tell it directly that we need SmoothPixmapTransform.
304 *
305 * So here is a workaround: we set a negligible scale to convince
306 * Qt we use a non-only-translating transform.
307 */
308 while (transform.type() == QTransform::TxTranslate) {
309 const qreal scale = transform.m11();
310 const qreal fakeScale = scale - 10 * std::numeric_limits<qreal>::epsilon();
311 transform *= QTransform::fromScale(fakeScale, fakeScale);
312 }
313
314 QPainter gc(&dstImage);
315 gc.setTransform(
316 QTransform::fromTranslate(-QPAINTER_WORKAROUND_BORDER,
317 -QPAINTER_WORKAROUND_BORDER) * transform);
318 gc.setRenderHints(QPainter::SmoothPixmapTransform);
319 gc.drawImage(QPointF(), srcImage);
320 gc.end();
321
322 return dstImage;
323 }
324
getClosest(QTransform transform,qreal * scale) const325 QImage KisQImagePyramid::getClosest(QTransform transform, qreal *scale) const
326 {
327 if (m_levels.isEmpty()) return QImage();
328
329 // Estimate scale
330 QSizeF transformedUnitSquare = transform.mapRect(QRectF(0, 0, 1, 1)).size();
331 qreal x = qAbs(transformedUnitSquare.width());
332 qreal y = qAbs(transformedUnitSquare.height());
333 qreal estimatedScale = (x > y) ? transformedUnitSquare.width() : transformedUnitSquare.height();
334
335 int level = findNearestLevel(estimatedScale, scale);
336 return m_levels[level].image;
337 }
338
getClosestWithoutWorkaroundBorder(QTransform transform,qreal * scale) const339 QImage KisQImagePyramid::getClosestWithoutWorkaroundBorder(QTransform transform, qreal *scale) const
340 {
341 QImage image = getClosest(transform, scale);
342 return image.copy(QPAINTER_WORKAROUND_BORDER,
343 QPAINTER_WORKAROUND_BORDER,
344 image.width() - 2 * QPAINTER_WORKAROUND_BORDER,
345 image.height() - 2 * QPAINTER_WORKAROUND_BORDER);
346 }
347