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