1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 */
17 #include "bitmapimage.h"
18 
19 #include <cmath>
20 #include <QDebug>
21 #include <QtMath>
22 #include <QFile>
23 #include <QPainterPath>
24 #include "util.h"
25 
BitmapImage()26 BitmapImage::BitmapImage()
27 {
28     mImage.reset(new QImage); // create null image
29     mBounds = QRect(0, 0, 0, 0);
30 }
31 
BitmapImage(const BitmapImage & a)32 BitmapImage::BitmapImage(const BitmapImage& a) : KeyFrame(a)
33 {
34     mBounds = a.mBounds;
35     mMinBound = a.mMinBound;
36     mEnableAutoCrop = a.mEnableAutoCrop;
37     mImage.reset(new QImage(*a.mImage));
38 }
39 
BitmapImage(const QRect & rectangle,const QColor & color)40 BitmapImage::BitmapImage(const QRect& rectangle, const QColor& color)
41 {
42     mBounds = rectangle;
43     mImage.reset(new QImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied));
44     mImage->fill(color.rgba());
45     mMinBound = false;
46 }
47 
BitmapImage(const QPoint & topLeft,const QImage & image)48 BitmapImage::BitmapImage(const QPoint& topLeft, const QImage& image)
49 {
50     mBounds = QRect(topLeft, image.size());
51     mMinBound = true;
52     mImage.reset(new QImage(image));
53 }
54 
BitmapImage(const QPoint & topLeft,const QString & path)55 BitmapImage::BitmapImage(const QPoint& topLeft, const QString& path)
56 {
57     setFileName(path);
58     mImage.reset();
59 
60     mBounds = QRect(topLeft, QSize(0, 0));
61     mMinBound = true;
62     setModified(false);
63 }
64 
~BitmapImage()65 BitmapImage::~BitmapImage()
66 {
67 }
68 
setImage(QImage * img)69 void BitmapImage::setImage(QImage* img)
70 {
71     Q_CHECK_PTR(img);
72     mImage.reset(img);
73     mMinBound = false;
74 
75     modification();
76 }
77 
operator =(const BitmapImage & a)78 BitmapImage& BitmapImage::operator=(const BitmapImage& a)
79 {
80     if (this == &a)
81     {
82         return *this; // a self-assignment
83     }
84 
85     KeyFrame::operator=(a);
86     mBounds = a.mBounds;
87     mMinBound = a.mMinBound;
88     mImage.reset(new QImage(*a.mImage));
89     modification();
90     return *this;
91 }
92 
clone()93 BitmapImage* BitmapImage::clone()
94 {
95     return new BitmapImage(*this);
96 }
97 
loadFile()98 void BitmapImage::loadFile()
99 {
100     if (mImage == nullptr)
101     {
102         mImage.reset(new QImage(fileName()));
103         mBounds.setSize(mImage->size());
104         mMinBound = false;
105     }
106 }
107 
unloadFile()108 void BitmapImage::unloadFile()
109 {
110     if (isModified() == false)
111     {
112         mImage.reset();
113     }
114 }
115 
isLoaded()116 bool BitmapImage::isLoaded()
117 {
118     return (mImage != nullptr);
119 }
120 
memoryUsage()121 quint64 BitmapImage::memoryUsage()
122 {
123     if (mImage)
124     {
125         return imageSize(*mImage);
126     }
127     return 0;
128 }
129 
paintImage(QPainter & painter)130 void BitmapImage::paintImage(QPainter& painter)
131 {
132     painter.drawImage(mBounds.topLeft(), *image());
133 }
134 
paintImage(QPainter & painter,QImage & image,QRect sourceRect,QRect destRect)135 void BitmapImage::paintImage(QPainter& painter, QImage& image, QRect sourceRect, QRect destRect)
136 {
137     painter.drawImage(QRect(mBounds.topLeft(), destRect.size()),
138                       image,
139                       sourceRect);
140 }
141 
image()142 QImage* BitmapImage::image()
143 {
144     loadFile();
145     return mImage.get();
146 }
147 
copy()148 BitmapImage BitmapImage::copy()
149 {
150     return BitmapImage(mBounds.topLeft(), *image());
151 }
152 
copy(QRect rectangle)153 BitmapImage BitmapImage::copy(QRect rectangle)
154 {
155     if (rectangle.isEmpty() || mBounds.isEmpty()) return BitmapImage();
156 
157     QRect intersection2 = rectangle.translated(-mBounds.topLeft());
158     BitmapImage result(rectangle.topLeft(), image()->copy(intersection2));
159     return result;
160 }
161 
paste(BitmapImage * bitmapImage,QPainter::CompositionMode cm)162 void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
163 {
164     if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
165     {
166         return;
167     }
168 
169     setCompositionModeBounds(bitmapImage, cm);
170 
171     QImage* image2 = bitmapImage->image();
172 
173     QPainter painter(image());
174     painter.setCompositionMode(cm);
175     painter.drawImage(bitmapImage->mBounds.topLeft() - mBounds.topLeft(), *image2);
176     painter.end();
177 
178     modification();
179 }
180 
moveTopLeft(QPoint point)181 void BitmapImage::moveTopLeft(QPoint point)
182 {
183     mBounds.moveTopLeft(point);
184     // Size is unchanged so there is no need to update mBounds
185     modification();
186 }
187 
transform(QRect newBoundaries,bool smoothTransform)188 void BitmapImage::transform(QRect newBoundaries, bool smoothTransform)
189 {
190     mBounds = newBoundaries;
191     newBoundaries.moveTopLeft(QPoint(0, 0));
192     QImage* newImage = new QImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
193 
194     QPainter painter(newImage);
195     painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
196     painter.setCompositionMode(QPainter::CompositionMode_Source);
197     painter.fillRect(newImage->rect(), QColor(0, 0, 0, 0));
198     painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
199     painter.drawImage(newBoundaries, *image());
200     painter.end();
201     mImage.reset(newImage);
202 
203     modification();
204 }
205 
transformed(QRect selection,QTransform transform,bool smoothTransform)206 BitmapImage BitmapImage::transformed(QRect selection, QTransform transform, bool smoothTransform)
207 {
208     Q_ASSERT(!selection.isEmpty());
209 
210     BitmapImage selectedPart = copy(selection);
211 
212     // Get the transformed image
213     QImage transformedImage;
214     if (smoothTransform)
215     {
216         transformedImage = selectedPart.image()->transformed(transform, Qt::SmoothTransformation);
217     }
218     else
219     {
220         transformedImage = selectedPart.image()->transformed(transform);
221     }
222     return BitmapImage(transform.mapRect(selection).normalized().topLeft(), transformedImage);
223 }
224 
transformed(QRect newBoundaries,bool smoothTransform)225 BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
226 {
227     BitmapImage transformedImage(newBoundaries, QColor(0, 0, 0, 0));
228     QPainter painter(transformedImage.image());
229     painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
230     newBoundaries.moveTopLeft(QPoint(0, 0));
231     painter.drawImage(newBoundaries, *image());
232     painter.end();
233     return transformedImage;
234 }
235 
236 /** Update image bounds.
237  *
238  *  @param[in] newBoundaries the new bounds
239  *
240  *  Sets this image's bounds to rectangle.
241  *  Modifies mBounds and crops mImage.
242  */
updateBounds(QRect newBoundaries)243 void BitmapImage::updateBounds(QRect newBoundaries)
244 {
245     // Check to make sure changes actually need to be made
246     if (mBounds == newBoundaries) return;
247 
248     QImage* newImage = new QImage( newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
249     newImage->fill(Qt::transparent);
250     if (!newImage->isNull())
251     {
252         QPainter painter(newImage);
253         painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *mImage);
254         painter.end();
255     }
256     mImage.reset( newImage );
257     mBounds = newBoundaries;
258     mMinBound = false;
259 
260     modification();
261 }
262 
extend(const QPoint & p)263 void BitmapImage::extend(const QPoint &p)
264 {
265     if (!mBounds.contains(p))
266     {
267         extend(QRect(p, QSize(1, 1)));
268     }
269 }
270 
extend(QRect rectangle)271 void BitmapImage::extend(QRect rectangle)
272 {
273     if (rectangle.width() <= 0) rectangle.setWidth(1);
274     if (rectangle.height() <= 0) rectangle.setHeight(1);
275     if (mBounds.contains(rectangle))
276     {
277         // Do nothing
278     }
279     else
280     {
281         QRect newBoundaries = mBounds.united(rectangle).normalized();
282         QImage* newImage = new QImage(newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
283         newImage->fill(Qt::transparent);
284         if (!newImage->isNull())
285         {
286             QPainter painter(newImage);
287             painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *image());
288             painter.end();
289         }
290         mImage.reset(newImage);
291         mBounds = newBoundaries;
292 
293         modification();
294     }
295 }
296 
297 /** Updates the bounds after a drawImage operation with the composition mode cm.
298  *
299  *  @param[in] source The source image used for the drawImage call.
300  *  @param[in] cm The composition mode that will be used for the draw image
301  *
302  *  @see BitmapImage::setCompositionModeBounds(BitmapImage, QPainter::CompositionMode)
303  */
setCompositionModeBounds(BitmapImage * source,QPainter::CompositionMode cm)304 void BitmapImage::setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm)
305 {
306     if (source)
307     {
308         setCompositionModeBounds(source->mBounds, source->mMinBound, cm);
309     }
310 }
311 
312 /** Updates the bounds after a draw operation with the composition mode cm.
313  *
314  * @param[in] sourceBounds The bounds of the source used for drawcall.
315  * @param[in] isSourceMinBounds Is sourceBounds the minimal bounds for the source image
316  * @param[in] cm The composition mode that will be used for the draw image
317  *
318  * For a call to draw image of a QPainter (initialized with mImage) with an argument
319  * of source, this function intelligently calculates the bounds. It will attempt to
320  * preserve minimum bounds based on the composition mode.
321  *
322  * This works baed on the principle that some minimal bounds can be determined
323  * solely by the minimal bounds of this and source, depending on the value of cm.
324  * Some composition modes only expand, or have no affect on the bounds.
325  *
326  * @warning The draw operation described by the arguments of this
327  *          function needs to be called after this function is run,
328  *          or the bounds will be out of sync. If mBounds is null,
329  *          no draw operation needs to be performed.
330  */
setCompositionModeBounds(QRect sourceBounds,bool isSourceMinBounds,QPainter::CompositionMode cm)331 void BitmapImage::setCompositionModeBounds(QRect sourceBounds, bool isSourceMinBounds, QPainter::CompositionMode cm)
332 {
333     QRect newBoundaries;
334     switch(cm)
335     {
336     case QPainter::CompositionMode_Destination:
337     case QPainter::CompositionMode_SourceAtop:
338         // The Destination and SourceAtop modes
339         // do not change the bounds from destination.
340         newBoundaries = mBounds;
341         // mMinBound remains the same
342         break;
343     case QPainter::CompositionMode_SourceIn:
344     case QPainter::CompositionMode_DestinationIn:
345     case QPainter::CompositionMode_Clear:
346     case QPainter::CompositionMode_DestinationOut:
347         // The bounds of the result of SourceIn, DestinationIn, Clear, and DestinationOut
348         // modes are no larger than the destination bounds
349         newBoundaries = mBounds;
350         mMinBound = false;
351         break;
352     default:
353         // If it's not one of the above cases, create a union of the two bounds.
354         // This contains the minimum bounds, if both the destination and source
355         // use their respective minimum bounds.
356         newBoundaries = mBounds.united(sourceBounds);
357         mMinBound = mMinBound && isSourceMinBounds;
358     }
359 
360     updateBounds(newBoundaries);
361 }
362 
363 /** Removes any transparent borders by reducing the boundaries.
364  *
365  *  This function reduces the bounds of an image until the top and
366  *  bottom rows, and the left and right columns of pixels each
367  *  contain at least one pixel with a non-zero alpha value
368  *  (i.e. non-transparent pixel). Both mBounds and
369  *  the size of #mImage are updated.
370  *
371  *  @pre mBounds.size() == mImage->size()
372  *  @post Either the first and last rows and columns all contain a
373  *        pixel with alpha > 0 or mBounds.isEmpty() == true
374  *  @post isMinimallyBounded() == true
375  */
autoCrop()376 void BitmapImage::autoCrop()
377 {
378     if (!mEnableAutoCrop) return;
379     if (mBounds.isEmpty()) return; // Exit if current bounds are null
380     if (!mImage) return;
381 
382     Q_ASSERT(mBounds.size() == mImage->size());
383 
384     // Exit if already min bounded
385     if (mMinBound) return;
386 
387     // Get image properties
388     const int width = mImage->width();
389 
390     // Relative top and bottom row indices (inclusive)
391     int relTop = 0;
392     int relBottom = mBounds.height()-1;
393 
394     // Check top row
395     bool isEmpty = true; // Used to track if a non-transparent pixel has been found
396     while (isEmpty && relTop <= relBottom) // Loop through rows
397     {
398         // Point cursor to the first pixel in the current top row
399         const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relTop));
400         for (int col = 0; col < width; col++) // Loop through pixels in row
401         {
402             // If the pixel is not transparent
403             // (i.e. alpha channel > 0)
404             if (qAlpha(*cursor) != 0)
405             {
406                 // We've found a non-transparent pixel in row relTop,
407                 // so we can stop looking for one
408                 isEmpty = false;
409                 break;
410             }
411             // Move cursor to point to the next pixel in the row
412             cursor++;
413         }
414         if (isEmpty)
415         {
416             // If the row we just checked was empty, increase relTop
417             // to remove the empty row from the top of the bounding box
418             ++relTop;
419         }
420     }
421 
422     // Check bottom row
423     isEmpty = true; // Reset isEmpty
424     while (isEmpty && relBottom >= relTop) // Loop through rows
425     {
426         // Point cursor to the first pixel in the current bottom row
427         const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relBottom));
428         for (int col = 0; col < width; col++) // Loop through pixels in row
429         {
430             // If the pixel is not transparent
431             // (i.e. alpha channel > 0)
432             if(qAlpha(*cursor) != 0)
433             {
434                 // We've found a non-transparent pixel in row relBottom,
435                 // so we can stop looking for one
436                 isEmpty = false;
437                 break;
438             }
439             // Move cursor to point to the next pixel in the row
440             ++cursor;
441         }
442         if (isEmpty)
443         {
444             // If the row we just checked was empty, decrease relBottom
445             // to remove the empty row from the bottom of the bounding box
446             --relBottom;
447         }
448     }
449 
450     // Relative left and right column indices (inclusive)
451     int relLeft = 0;
452     int relRight = mBounds.width()-1;
453 
454     // Check left row
455     isEmpty = (relBottom >= relTop); // Check left only when
456     while (isEmpty && relBottom >= relTop && relLeft <= relRight) // Loop through columns
457     {
458         // Point cursor to the pixel at row relTop and column relLeft
459         const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relTop)) + relLeft;
460         // Loop through pixels in column
461         // Note: we only need to loop from relTop to relBottom (inclusive)
462         //       not the full image height, because rows 0 to relTop-1 and
463         //       relBottom+1 to mBounds.height() have already been
464         //       confirmed to contain only transparent pixels
465         for (int row = relTop; row <= relBottom; row++)
466         {
467             // If the pixel is not transparent
468             // (i.e. alpha channel > 0)
469             if(qAlpha(*cursor) != 0)
470             {
471                 // We've found a non-transparent pixel in column relLeft,
472                 // so we can stop looking for one
473                 isEmpty = false;
474                 break;
475             }
476             // Move cursor to point to next pixel in the column
477             // Increment by width because the data is in row-major order
478             cursor += width;
479         }
480         if (isEmpty)
481         {
482             // If the column we just checked was empty, increase relLeft
483             // to remove the empty column from the left of the bounding box
484             ++relLeft;
485         }
486     }
487 
488     // Check right row
489     isEmpty = (relBottom >= relTop); // Reset isEmpty
490     while (isEmpty && relRight >= relLeft) // Loop through columns
491     {
492         // Point cursor to the pixel at row relTop and column relRight
493         const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relTop)) + relRight;
494         // Loop through pixels in column
495         // Note: we only need to loop from relTop to relBottom (inclusive)
496         //       not the full image height, because rows 0 to relTop-1 and
497         //       relBottom+1 to mBounds.height()-1 have already been
498         //       confirmed to contain only transparent pixels
499         for (int row = relTop; row <= relBottom; row++)
500         {
501             // If the pixel is not transparent
502             // (i.e. alpha channel > 0)
503             if(qAlpha(*cursor) != 0)
504             {
505                 // We've found a non-transparent pixel in column relRight,
506                 // so we can stop looking for one
507                 isEmpty = false;
508                 break;
509             }
510             // Move cursor to point to next pixel in the column
511             // Increment by width because the data is in row-major order
512             cursor += width;
513         }
514         if (isEmpty)
515         {
516             // If the column we just checked was empty, increase relRight
517             // to remove the empty column from the left of the bounding box
518             --relRight;
519         }
520     }
521 
522     //qDebug() << "Original" << mBounds;
523     //qDebug() << "Autocrop" << relLeft << relTop << relRight - mBounds.width() + 1 << relBottom - mBounds.height() + 1;
524     // Update mBounds and mImage if necessary
525     updateBounds(mBounds.adjusted(relLeft, relTop, relRight - mBounds.width() + 1, relBottom - mBounds.height() + 1));
526 
527     //qDebug() << "New bounds" << mBounds;
528 
529     mMinBound = true;
530 }
531 
pixel(int x,int y)532 QRgb BitmapImage::pixel(int x, int y)
533 {
534     return pixel(QPoint(x, y));
535 }
536 
pixel(QPoint p)537 QRgb BitmapImage::pixel(QPoint p)
538 {
539     QRgb result = qRgba(0, 0, 0, 0); // black
540     if (mBounds.contains(p))
541         result = image()->pixel(p - mBounds.topLeft());
542     return result;
543 }
544 
setPixel(int x,int y,QRgb color)545 void BitmapImage::setPixel(int x, int y, QRgb color)
546 {
547     setPixel(QPoint(x, y), color);
548 }
549 
setPixel(QPoint p,QRgb color)550 void BitmapImage::setPixel(QPoint p, QRgb color)
551 {
552     setCompositionModeBounds(QRect(p, QSize(1,1)), true, QPainter::CompositionMode_SourceOver);
553     if (mBounds.contains(p))
554     {
555         image()->setPixel(p - mBounds.topLeft(), color);
556     }
557     modification();
558 }
559 
fillNonAlphaPixels(const QRgb color)560 void BitmapImage::fillNonAlphaPixels(const QRgb color)
561 {
562     if (mBounds.isEmpty()) { return; }
563 
564     BitmapImage fill(bounds(), color);
565     paste(&fill, QPainter::CompositionMode_SourceIn);
566 }
567 
drawLine(QPointF P1,QPointF P2,QPen pen,QPainter::CompositionMode cm,bool antialiasing)568 void BitmapImage::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing)
569 {
570     int width = 2 + pen.width();
571     setCompositionModeBounds(QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width, -width, width, width), true, cm);
572     if (!image()->isNull())
573     {
574         QPainter painter(image());
575         painter.setCompositionMode(cm);
576         painter.setRenderHint(QPainter::Antialiasing, antialiasing);
577         painter.setPen(pen);
578         painter.drawLine(P1 - mBounds.topLeft(), P2 - mBounds.topLeft());
579         painter.end();
580     }
581     modification();
582 }
583 
drawRect(QRectF rectangle,QPen pen,QBrush brush,QPainter::CompositionMode cm,bool antialiasing)584 void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
585 {
586     int width = pen.width();
587     setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
588     if (brush.style() == Qt::RadialGradientPattern)
589     {
590         QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
591         gradient->setCenter(gradient->center() - mBounds.topLeft());
592         gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
593     }
594     if (!image()->isNull())
595     {
596         QPainter painter(image());
597         painter.setCompositionMode(cm);
598         painter.setRenderHint(QPainter::Antialiasing, antialiasing);
599         painter.setPen(pen);
600         painter.setBrush(brush);
601         painter.drawRect(rectangle.translated(-mBounds.topLeft()));
602         painter.end();
603     }
604     modification();
605 }
606 
drawEllipse(QRectF rectangle,QPen pen,QBrush brush,QPainter::CompositionMode cm,bool antialiasing)607 void BitmapImage::drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
608 {
609     int width = pen.width();
610     setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
611     if (brush.style() == Qt::RadialGradientPattern)
612     {
613         QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
614         gradient->setCenter(gradient->center() - mBounds.topLeft());
615         gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
616     }
617     if (!image()->isNull())
618     {
619         QPainter painter(image());
620 
621         painter.setRenderHint(QPainter::Antialiasing, antialiasing);
622         painter.setPen(pen);
623         painter.setBrush(brush);
624         painter.setCompositionMode(cm);
625         painter.drawEllipse(rectangle.translated(-mBounds.topLeft()));
626         painter.end();
627     }
628     modification();
629 }
630 
drawPath(QPainterPath path,QPen pen,QBrush brush,QPainter::CompositionMode cm,bool antialiasing)631 void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush,
632                            QPainter::CompositionMode cm, bool antialiasing)
633 {
634     int width = pen.width();
635     // qreal inc = 1.0 + width / 20.0;
636 
637     setCompositionModeBounds(path.controlPointRect().adjusted(-width, -width, width, width).toRect(), true, cm);
638 
639     if (!image()->isNull())
640     {
641         QPainter painter(image());
642         painter.setCompositionMode(cm);
643         painter.setRenderHint(QPainter::Antialiasing, antialiasing);
644         painter.setPen(pen);
645         painter.setBrush(brush);
646         painter.setTransform(QTransform().translate(-mBounds.left(), -mBounds.top()));
647         painter.setWorldMatrixEnabled(true);
648         if (path.length() > 0)
649         {
650             /*
651             for (int pt = 0; pt < path.elementCount() - 1; pt++)
652             {
653                 qreal dx = path.elementAt(pt + 1).x - path.elementAt(pt).x;
654                 qreal dy = path.elementAt(pt + 1).y - path.elementAt(pt).y;
655                 qreal m = sqrt(dx*dx + dy*dy);
656                 qreal factorx = dx / m;
657                 qreal factory = dy / m;
658                 for (float h = 0.f; h < m; h += inc)
659                 {
660                     qreal x = path.elementAt(pt).x + factorx * h;
661                     qreal y = path.elementAt(pt).y + factory * h;
662                     painter.drawPoint(QPointF(x, y));
663                 }
664             }
665             */
666             painter.drawPath( path );
667         }
668         else
669         {
670             // forces drawing when points are coincident (mousedown)
671             painter.drawPoint(static_cast<int>(path.elementAt(0).x), static_cast<int>(path.elementAt(0).y));
672         }
673         painter.end();
674     }
675     modification();
676 }
677 
findLeft(QRectF rect,int grayValue)678 PegbarResult BitmapImage::findLeft(QRectF rect, int grayValue)
679 {
680     PegbarResult result;
681     result.value = -1;
682     result.errorcode = Status::FAIL;
683     int left = static_cast<int>(rect.left());
684     int right = static_cast<int>(rect.right());
685     int top = static_cast<int>(rect.top());
686     int bottom = static_cast<int>(rect.bottom());
687     for (int x = left; x <= right; x++)
688     {
689         for (int y = top; y <= bottom; y++)
690         {
691             if (qAlpha(constScanLine(x,y)) == 255 && qGray(constScanLine(x,y)) < grayValue)
692             {
693                 result.value = x;
694                 result.errorcode = Status::OK;
695                 return result;
696             }
697         }
698     }
699     return result;
700 }
701 
findTop(QRectF rect,int grayValue)702 PegbarResult BitmapImage::findTop(QRectF rect, int grayValue)
703 {
704     PegbarResult result;
705     result.value = -1;
706     result.errorcode = Status::FAIL;
707     int left = static_cast<int>(rect.left());
708     int right = static_cast<int>(rect.right());
709     int top = static_cast<int>(rect.top());
710     int bottom = static_cast<int>(rect.bottom());
711     for (int y = top; y <= bottom; y++)
712     {
713         for (int x = left; x <= right; x++)
714         {
715             if (qAlpha(constScanLine(x,y)) == 255 && qGray(constScanLine(x,y)) < grayValue)
716             {
717                 result.value = y;
718                 result.errorcode = Status::OK;
719                 return result;
720             }
721         }
722     }
723     return result;
724 }
725 
writeFile(const QString & filename)726 Status BitmapImage::writeFile(const QString& filename)
727 {
728     if (mImage && !mImage->isNull())
729     {
730         bool b = mImage->save(filename);
731         return (b) ? Status::OK : Status::FAIL;
732     }
733 
734     if (bounds().isEmpty())
735     {
736         QFile f(filename);
737         if(f.exists())
738         {
739             bool b = f.remove();
740             return (b) ? Status::OK : Status::FAIL;
741         }
742         return Status::SAFE;
743     }
744     return Status::SAFE;
745 }
746 
clear()747 void BitmapImage::clear()
748 {
749     mImage.reset(new QImage); // null image
750     mBounds = QRect(0, 0, 0, 0);
751     mMinBound = true;
752     modification();
753 }
754 
constScanLine(int x,int y) const755 QRgb BitmapImage::constScanLine(int x, int y) const
756 {
757     QRgb result = qRgba(0, 0, 0, 0);
758     if (mBounds.contains(QPoint(x, y)))
759     {
760         result = *(reinterpret_cast<const QRgb*>(mImage->constScanLine(y - mBounds.top())) + x - mBounds.left());
761     }
762     return result;
763 }
764 
scanLine(int x,int y,QRgb color)765 void BitmapImage::scanLine(int x, int y, QRgb color)
766 {
767     extend(QPoint(x, y));
768     if (mBounds.contains(QPoint(x, y)))
769     {
770         // Make sure color is premultiplied before calling
771         *(reinterpret_cast<QRgb*>(image()->scanLine(y - mBounds.top())) + x - mBounds.left()) =
772             qRgba(
773                 qRed(color),
774                 qGreen(color),
775                 qBlue(color),
776                 qAlpha(color));
777     }
778 }
779 
clear(QRect rectangle)780 void BitmapImage::clear(QRect rectangle)
781 {
782     QRect clearRectangle = mBounds.intersected(rectangle);
783     clearRectangle.moveTopLeft(clearRectangle.topLeft() - mBounds.topLeft());
784 
785     setCompositionModeBounds(clearRectangle, true, QPainter::CompositionMode_Clear);
786 
787     QPainter painter(image());
788     painter.setCompositionMode(QPainter::CompositionMode_Clear);
789     painter.fillRect(clearRectangle, QColor(0, 0, 0, 0));
790     painter.end();
791 
792     modification();
793 }
794 
795 /** Compare colors for the purposes of flood filling
796  *
797  *  Calculates the Eulcidian difference of the RGB channels
798  *  of the image and compares it to the tolerance
799  *
800  *  @param[in] newColor The first color to compare
801  *  @param[in] oldColor The second color to compare
802  *  @param[in] tolerance The threshold limit between a matching and non-matching color
803  *  @param[in,out] cache Contains a mapping of previous results of compareColor with rule that
804  *                 cache[someColor] = compareColor(someColor, oldColor, tolerance)
805  *
806  *  @return Returns true if the colors have a similarity below the tolerance level
807  *          (i.e. if Eulcidian distance squared is <= tolerance)
808  */
compareColor(QRgb newColor,QRgb oldColor,int tolerance,QHash<QRgb,bool> * cache)809 bool BitmapImage::compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash<QRgb, bool> *cache)
810 {
811     // Handle trivial case
812     if (newColor == oldColor) return true;
813 
814     if(cache && cache->contains(newColor)) return cache->value(newColor);
815 
816     // Get Eulcidian distance between colors
817     // Not an accurate representation of human perception,
818     // but it's the best any image editing program ever does
819     int diffRed = static_cast<int>(qPow(qRed(oldColor) - qRed(newColor), 2));
820     int diffGreen = static_cast<int>(qPow(qGreen(oldColor) - qGreen(newColor), 2));
821     int diffBlue = static_cast<int>(qPow(qBlue(oldColor) - qBlue(newColor), 2));
822     // This may not be the best way to handle alpha since the other channels become less relevant as
823     // the alpha is reduces (ex. QColor(0,0,0,0) is the same as QColor(255,255,255,0))
824     int diffAlpha = static_cast<int>(qPow(qAlpha(oldColor) - qAlpha(newColor), 2));
825 
826     bool isSimilar = (diffRed + diffGreen + diffBlue + diffAlpha) <= tolerance;
827 
828     if(cache)
829     {
830         Q_ASSERT(cache->contains(isSimilar) ? isSimilar == (*cache)[newColor] : true);
831         (*cache)[newColor] = isSimilar;
832     }
833 
834     return isSimilar;
835 }
836 
837 // Flood fill
838 // ----- http://lodev.org/cgtutor/floodfill.html
floodFill(BitmapImage * targetImage,QRect cameraRect,QPoint point,QRgb newColor,int tolerance)839 void BitmapImage::floodFill(BitmapImage* targetImage,
840                             QRect cameraRect,
841                             QPoint point,
842                             QRgb newColor,
843                             int tolerance)
844 {
845     // If the point we are supposed to fill is outside the image and camera bounds, do nothing
846     if(!cameraRect.united(targetImage->bounds()).contains(point))
847     {
848         return;
849     }
850 
851     // Square tolerance for use with compareColor
852     tolerance = static_cast<int>(qPow(tolerance, 2));
853 
854     QRgb oldColor = targetImage->pixel(point);
855     oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
856 
857     // Preparations
858     QList<QPoint> queue; // queue all the pixels of the filled area (as they are found)
859 
860     BitmapImage* replaceImage = nullptr;
861     QPoint tempPoint;
862     QRgb newPlacedColor = 0;
863     QScopedPointer< QHash<QRgb, bool> > cache(new QHash<QRgb, bool>());
864 
865     int xTemp = 0;
866     bool spanLeft = false;
867     bool spanRight = false;
868 
869     // Extend to size of Camera
870     targetImage->extend(cameraRect);
871     replaceImage = new BitmapImage(targetImage->mBounds, Qt::transparent);
872 
873     queue.append(point);
874     // Preparations END
875 
876     while (!queue.empty())
877     {
878         tempPoint = queue.takeFirst();
879 
880         point.setX(tempPoint.x());
881         point.setY(tempPoint.y());
882 
883         xTemp = point.x();
884 
885         newPlacedColor = replaceImage->constScanLine(xTemp, point.y());
886         while (xTemp >= targetImage->mBounds.left() &&
887                compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data())) xTemp--;
888         xTemp++;
889 
890         spanLeft = spanRight = false;
891         while (xTemp <= targetImage->mBounds.right() &&
892                compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data()) &&
893                newPlacedColor != newColor)
894         {
895 
896             // Set pixel color
897             replaceImage->scanLine(xTemp, point.y(), newColor);
898 
899             if (!spanLeft && (point.y() > targetImage->mBounds.top()) &&
900                 compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
901                 queue.append(QPoint(xTemp, point.y() - 1));
902                 spanLeft = true;
903             }
904             else if (spanLeft && (point.y() > targetImage->mBounds.top()) &&
905                      !compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
906                 spanLeft = false;
907             }
908 
909             if (!spanRight && point.y() < targetImage->mBounds.bottom() &&
910                 compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
911                 queue.append(QPoint(xTemp, point.y() + 1));
912                 spanRight = true;
913 
914             }
915             else if (spanRight && point.y() < targetImage->mBounds.bottom() &&
916                      !compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
917                 spanRight = false;
918             }
919 
920             Q_ASSERT(queue.count() < (targetImage->mBounds.width() * targetImage->mBounds.height()));
921             xTemp++;
922         }
923     }
924 
925     targetImage->paste(replaceImage);
926     targetImage->modification();
927     delete replaceImage;
928 }
929