All Classes Namespaces Functions Variables Enumerations Properties Pages
bitmapimage.cpp
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 
26 BitmapImage::BitmapImage()
27 {
28  mImage.reset(new QImage); // create null image
29  mBounds = QRect(0, 0, 0, 0);
30 }
31 
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 
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 
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 
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 
65 BitmapImage::~BitmapImage()
66 {
67 }
68 
69 void BitmapImage::setImage(QImage* img)
70 {
71  Q_CHECK_PTR(img);
72  mImage.reset(img);
73  mMinBound = false;
74 
75  modification();
76 }
77 
78 BitmapImage& BitmapImage::operator=(const BitmapImage& a)
79 {
80  mBounds = a.mBounds;
81  mMinBound = a.mMinBound;
82  mImage.reset(new QImage(*a.mImage));
83  modification();
84  return *this;
85 }
86 
87 BitmapImage* BitmapImage::clone()
88 {
89  return new BitmapImage(*this);
90 }
91 
92 void BitmapImage::loadFile()
93 {
94  if (mImage == nullptr)
95  {
96  mImage.reset(new QImage(fileName()));
97  mBounds.setSize(mImage->size());
98  mMinBound = false;
99  }
100 }
101 
102 void BitmapImage::unloadFile()
103 {
104  if (isModified() == false)
105  {
106  mImage.reset();
107  }
108 }
109 
110 bool BitmapImage::isLoaded()
111 {
112  return (mImage != nullptr);
113 }
114 
115 quint64 BitmapImage::memoryUsage()
116 {
117  if (mImage)
118  {
119  return imageSize(*mImage);
120  }
121  return 0;
122 }
123 
124 void BitmapImage::paintImage(QPainter& painter)
125 {
126  painter.drawImage(mBounds.topLeft(), *image());
127 }
128 
129 void BitmapImage::paintImage(QPainter& painter, QImage& image, QRect sourceRect, QRect destRect)
130 {
131  painter.drawImage(QRect(mBounds.topLeft(), destRect.size()),
132  image,
133  sourceRect);
134 }
135 
136 QImage* BitmapImage::image()
137 {
138  loadFile();
139  return mImage.get();
140 }
141 
142 BitmapImage BitmapImage::copy()
143 {
144  return BitmapImage(mBounds.topLeft(), *image());
145 }
146 
147 BitmapImage BitmapImage::copy(QRect rectangle)
148 {
149  if (rectangle.isEmpty() || mBounds.isEmpty()) return BitmapImage();
150 
151  QRect intersection2 = rectangle.translated(-mBounds.topLeft());
152  BitmapImage result = BitmapImage(rectangle.topLeft(), image()->copy(intersection2));
153  return result;
154 }
155 
156 void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
157 {
158  if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
159  {
160  return;
161  }
162 
163  setCompositionModeBounds(bitmapImage, cm);
164 
165  QImage* image2 = bitmapImage->image();
166 
167  QPainter painter(image());
168  painter.setCompositionMode(cm);
169  painter.drawImage(bitmapImage->mBounds.topLeft() - mBounds.topLeft(), *image2);
170  painter.end();
171 
172  modification();
173 }
174 
175 void BitmapImage::moveTopLeft(QPoint point)
176 {
177  mBounds.moveTopLeft(point);
178  // Size is unchanged so there is no need to update mBounds
179  modification();
180 }
181 
182 void BitmapImage::transform(QRect newBoundaries, bool smoothTransform)
183 {
184  mBounds = newBoundaries;
185  newBoundaries.moveTopLeft(QPoint(0, 0));
186  QImage* newImage = new QImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
187 
188  QPainter painter(newImage);
189  painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
191  painter.fillRect(newImage->rect(), QColor(0, 0, 0, 0));
193  painter.drawImage(newBoundaries, *image());
194  painter.end();
195  mImage.reset(newImage);
196 
197  modification();
198 }
199 
200 BitmapImage BitmapImage::transformed(QRect selection, QTransform transform, bool smoothTransform)
201 {
202  Q_ASSERT(!selection.isEmpty());
203 
204  BitmapImage selectedPart = copy(selection);
205 
206  // Get the transformed image
207  QImage transformedImage;
208  if (smoothTransform)
209  {
210  transformedImage = selectedPart.image()->transformed(transform, Qt::SmoothTransformation);
211  }
212  else
213  {
214  transformedImage = selectedPart.image()->transformed(transform);
215  }
216  return BitmapImage(transform.mapRect(selection).normalized().topLeft(), transformedImage);
217 }
218 
219 BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
220 {
221  BitmapImage transformedImage(newBoundaries, QColor(0, 0, 0, 0));
222  QPainter painter(transformedImage.image());
223  painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
224  newBoundaries.moveTopLeft(QPoint(0, 0));
225  painter.drawImage(newBoundaries, *image());
226  painter.end();
227  return transformedImage;
228 }
229 
237 void BitmapImage::updateBounds(QRect newBoundaries)
238 {
239  // Check to make sure changes actually need to be made
240  if (mBounds == newBoundaries) return;
241 
242  QImage* newImage = new QImage( newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
243  newImage->fill(Qt::transparent);
244  if (!newImage->isNull())
245  {
246  QPainter painter(newImage);
247  painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *mImage);
248  painter.end();
249  }
250  mImage.reset( newImage );
251  mBounds = newBoundaries;
252  mMinBound = false;
253 
254  modification();
255 }
256 
257 void BitmapImage::extend(const QPoint &p)
258 {
259  if (!mBounds.contains(p))
260  {
261  extend(QRect(p, QSize(1, 1)));
262  }
263 }
264 
265 void BitmapImage::extend(QRect rectangle)
266 {
267  if (rectangle.width() <= 0) rectangle.setWidth(1);
268  if (rectangle.height() <= 0) rectangle.setHeight(1);
269  if (mBounds.contains(rectangle))
270  {
271  // Do nothing
272  }
273  else
274  {
275  QRect newBoundaries = mBounds.united(rectangle).normalized();
276  QImage* newImage = new QImage(newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
277  newImage->fill(Qt::transparent);
278  if (!newImage->isNull())
279  {
280  QPainter painter(newImage);
281  painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *image());
282  painter.end();
283  }
284  mImage.reset(newImage);
285  mBounds = newBoundaries;
286 
287  modification();
288  }
289 }
290 
299 {
300  if (source)
301  {
302  setCompositionModeBounds(source->mBounds, source->mMinBound, cm);
303  }
304 }
305 
325 void BitmapImage::setCompositionModeBounds(QRect sourceBounds, bool isSourceMinBounds, QPainter::CompositionMode cm)
326 {
327  QRect newBoundaries;
328  switch(cm)
329  {
332  // The Destination and SourceAtop modes
333  // do not change the bounds from destination.
334  newBoundaries = mBounds;
335  // mMinBound remains the same
336  break;
341  // The bounds of the result of SourceIn, DestinationIn, Clear, and DestinationOut
342  // modes are no larger than the destination bounds
343  newBoundaries = mBounds;
344  mMinBound = false;
345  break;
346  default:
347  // If it's not one of the above cases, create a union of the two bounds.
348  // This contains the minimum bounds, if both the destination and source
349  // use their respective minimum bounds.
350  newBoundaries = mBounds.united(sourceBounds);
351  mMinBound = mMinBound && isSourceMinBounds;
352  }
353 
354  updateBounds(newBoundaries);
355 }
356 
371 {
372  if (!mEnableAutoCrop) return;
373  if (mBounds.isEmpty()) return; // Exit if current bounds are null
374  if (!mImage) return;
375 
376  Q_ASSERT(mBounds.size() == mImage->size());
377 
378  // Exit if already min bounded
379  if (mMinBound) return;
380 
381  // Get image properties
382  const int width = mImage->width();
383 
384  // Relative top and bottom row indices (inclusive)
385  int relTop = 0;
386  int relBottom = mBounds.height()-1;
387 
388  // Check top row
389  bool isEmpty = true; // Used to track if a non-transparent pixel has been found
390  while (isEmpty && relTop <= relBottom) // Loop through rows
391  {
392  // Point cursor to the first pixel in the current top row
393  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relTop));
394  for (int col = 0; col < width; col++) // Loop through pixels in row
395  {
396  // If the pixel is not transparent
397  // (i.e. alpha channel > 0)
398  if (qAlpha(*cursor) != 0)
399  {
400  // We've found a non-transparent pixel in row relTop,
401  // so we can stop looking for one
402  isEmpty = false;
403  break;
404  }
405  // Move cursor to point to the next pixel in the row
406  cursor++;
407  }
408  if (isEmpty)
409  {
410  // If the row we just checked was empty, increase relTop
411  // to remove the empty row from the top of the bounding box
412  ++relTop;
413  }
414  }
415 
416  // Check bottom row
417  isEmpty = true; // Reset isEmpty
418  while (isEmpty && relBottom >= relTop) // Loop through rows
419  {
420  // Point cursor to the first pixel in the current bottom row
421  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relBottom));
422  for (int col = 0; col < width; col++) // Loop through pixels in row
423  {
424  // If the pixel is not transparent
425  // (i.e. alpha channel > 0)
426  if(qAlpha(*cursor) != 0)
427  {
428  // We've found a non-transparent pixel in row relBottom,
429  // so we can stop looking for one
430  isEmpty = false;
431  break;
432  }
433  // Move cursor to point to the next pixel in the row
434  ++cursor;
435  }
436  if (isEmpty)
437  {
438  // If the row we just checked was empty, decrease relBottom
439  // to remove the empty row from the bottom of the bounding box
440  --relBottom;
441  }
442  }
443 
444  // Relative left and right column indices (inclusive)
445  int relLeft = 0;
446  int relRight = mBounds.width()-1;
447 
448  // Check left row
449  isEmpty = (relBottom >= relTop); // Check left only when
450  while (isEmpty && relBottom >= relTop && relLeft <= relRight) // Loop through columns
451  {
452  // Point cursor to the pixel at row relTop and column relLeft
453  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relTop)) + relLeft;
454  // Loop through pixels in column
455  // Note: we only need to loop from relTop to relBottom (inclusive)
456  // not the full image height, because rows 0 to relTop-1 and
457  // relBottom+1 to mBounds.height() have already been
458  // confirmed to contain only transparent pixels
459  for (int row = relTop; row <= relBottom; row++)
460  {
461  // If the pixel is not transparent
462  // (i.e. alpha channel > 0)
463  if(qAlpha(*cursor) != 0)
464  {
465  // We've found a non-transparent pixel in column relLeft,
466  // so we can stop looking for one
467  isEmpty = false;
468  break;
469  }
470  // Move cursor to point to next pixel in the column
471  // Increment by width because the data is in row-major order
472  cursor += width;
473  }
474  if (isEmpty)
475  {
476  // If the column we just checked was empty, increase relLeft
477  // to remove the empty column from the left of the bounding box
478  ++relLeft;
479  }
480  }
481 
482  // Check right row
483  isEmpty = (relBottom >= relTop); // Reset isEmpty
484  while (isEmpty && relRight >= relLeft) // Loop through columns
485  {
486  // Point cursor to the pixel at row relTop and column relRight
487  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage->constScanLine(relTop)) + relRight;
488  // Loop through pixels in column
489  // Note: we only need to loop from relTop to relBottom (inclusive)
490  // not the full image height, because rows 0 to relTop-1 and
491  // relBottom+1 to mBounds.height()-1 have already been
492  // confirmed to contain only transparent pixels
493  for (int row = relTop; row <= relBottom; row++)
494  {
495  // If the pixel is not transparent
496  // (i.e. alpha channel > 0)
497  if(qAlpha(*cursor) != 0)
498  {
499  // We've found a non-transparent pixel in column relRight,
500  // so we can stop looking for one
501  isEmpty = false;
502  break;
503  }
504  // Move cursor to point to next pixel in the column
505  // Increment by width because the data is in row-major order
506  cursor += width;
507  }
508  if (isEmpty)
509  {
510  // If the column we just checked was empty, increase relRight
511  // to remove the empty column from the left of the bounding box
512  --relRight;
513  }
514  }
515 
516  //qDebug() << "Original" << mBounds;
517  //qDebug() << "Autocrop" << relLeft << relTop << relRight - mBounds.width() + 1 << relBottom - mBounds.height() + 1;
518  // Update mBounds and mImage if necessary
519  updateBounds(mBounds.adjusted(relLeft, relTop, relRight - mBounds.width() + 1, relBottom - mBounds.height() + 1));
520 
521  //qDebug() << "New bounds" << mBounds;
522 
523  mMinBound = true;
524 }
525 
526 QRgb BitmapImage::pixel(int x, int y)
527 {
528  return pixel(QPoint(x, y));
529 }
530 
531 QRgb BitmapImage::pixel(QPoint p)
532 {
533  QRgb result = qRgba(0, 0, 0, 0); // black
534  if (mBounds.contains(p))
535  result = image()->pixel(p - mBounds.topLeft());
536  return result;
537 }
538 
539 void BitmapImage::setPixel(int x, int y, QRgb color)
540 {
541  setPixel(QPoint(x, y), color);
542 }
543 
544 void BitmapImage::setPixel(QPoint p, QRgb color)
545 {
547  if (mBounds.contains(p))
548  {
549  image()->setPixel(p - mBounds.topLeft(), color);
550  }
551  modification();
552 }
553 
554 void BitmapImage::fillNonAlphaPixels(const QRgb color)
555 {
556  if (mBounds.isEmpty()) { return; }
557 
558  BitmapImage fill(bounds(), color);
560 }
561 
562 void BitmapImage::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing)
563 {
564  int width = 2 + pen.width();
565  setCompositionModeBounds(QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width, -width, width, width), true, cm);
566  if (!image()->isNull())
567  {
568  QPainter painter(image());
569  painter.setCompositionMode(cm);
570  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
571  painter.setPen(pen);
572  painter.drawLine(P1 - mBounds.topLeft(), P2 - mBounds.topLeft());
573  painter.end();
574  }
575  modification();
576 }
577 
578 void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
579 {
580  int width = pen.width();
581  setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
582  if (brush.style() == Qt::RadialGradientPattern)
583  {
584  QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
585  gradient->setCenter(gradient->center() - mBounds.topLeft());
586  gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
587  }
588  if (!image()->isNull())
589  {
590  QPainter painter(image());
591  painter.setCompositionMode(cm);
592  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
593  painter.setPen(pen);
594  painter.setBrush(brush);
595  painter.drawRect(rectangle.translated(-mBounds.topLeft()));
596  painter.end();
597  }
598  modification();
599 }
600 
601 void BitmapImage::drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
602 {
603  int width = pen.width();
604  setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
605  if (brush.style() == Qt::RadialGradientPattern)
606  {
607  QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
608  gradient->setCenter(gradient->center() - mBounds.topLeft());
609  gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
610  }
611  if (!image()->isNull())
612  {
613  QPainter painter(image());
614 
615  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
616  painter.setPen(pen);
617  painter.setBrush(brush);
618  painter.setCompositionMode(cm);
619  painter.drawEllipse(rectangle.translated(-mBounds.topLeft()));
620  painter.end();
621  }
622  modification();
623 }
624 
625 void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush,
626  QPainter::CompositionMode cm, bool antialiasing)
627 {
628  int width = pen.width();
629  // qreal inc = 1.0 + width / 20.0;
630 
631  setCompositionModeBounds(path.controlPointRect().adjusted(-width, -width, width, width).toRect(), true, cm);
632 
633  if (!image()->isNull())
634  {
635  QPainter painter(image());
636  painter.setCompositionMode(cm);
637  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
638  painter.setPen(pen);
639  painter.setBrush(brush);
640  painter.setTransform(QTransform().translate(-mBounds.left(), -mBounds.top()));
641  painter.setWorldMatrixEnabled(true);
642  if (path.length() > 0)
643  {
644  /*
645  for (int pt = 0; pt < path.elementCount() - 1; pt++)
646  {
647  qreal dx = path.elementAt(pt + 1).x - path.elementAt(pt).x;
648  qreal dy = path.elementAt(pt + 1).y - path.elementAt(pt).y;
649  qreal m = sqrt(dx*dx + dy*dy);
650  qreal factorx = dx / m;
651  qreal factory = dy / m;
652  for (float h = 0.f; h < m; h += inc)
653  {
654  qreal x = path.elementAt(pt).x + factorx * h;
655  qreal y = path.elementAt(pt).y + factory * h;
656  painter.drawPoint(QPointF(x, y));
657  }
658  }
659  */
660  painter.drawPath( path );
661  }
662  else
663  {
664  // forces drawing when points are coincident (mousedown)
665  painter.drawPoint(static_cast<int>(path.elementAt(0).x), static_cast<int>(path.elementAt(0).y));
666  }
667  painter.end();
668  }
669  modification();
670 }
671 
672 PegbarResult BitmapImage::findLeft(QRectF rect, int grayValue)
673 {
674  PegbarResult result;
675  result.value = -1;
676  result.errorcode = Status::FAIL;
677  int left = static_cast<int>(rect.left());
678  int right = static_cast<int>(rect.right());
679  int top = static_cast<int>(rect.top());
680  int bottom = static_cast<int>(rect.bottom());
681  for (int x = left; x <= right; x++)
682  {
683  for (int y = top; y <= bottom; y++)
684  {
685  if (qAlpha(constScanLine(x,y)) == 255 && qGray(constScanLine(x,y)) < grayValue)
686  {
687  result.value = x;
688  result.errorcode = Status::OK;
689  return result;
690  }
691  }
692  }
693  return result;
694 }
695 
696 PegbarResult BitmapImage::findTop(QRectF rect, int grayValue)
697 {
698  PegbarResult result;
699  result.value = -1;
700  result.errorcode = Status::FAIL;
701  int left = static_cast<int>(rect.left());
702  int right = static_cast<int>(rect.right());
703  int top = static_cast<int>(rect.top());
704  int bottom = static_cast<int>(rect.bottom());
705  for (int y = top; y <= bottom; y++)
706  {
707  for (int x = left; x <= right; x++)
708  {
709  if (qAlpha(constScanLine(x,y)) == 255 && qGray(constScanLine(x,y)) < grayValue)
710  {
711  result.value = y;
712  result.errorcode = Status::OK;
713  return result;
714  }
715  }
716  }
717  return result;
718 }
719 
720 Status BitmapImage::writeFile(const QString& filename)
721 {
722  if (mImage && !mImage->isNull())
723  {
724  bool b = mImage->save(filename);
725  return (b) ? Status::OK : Status::FAIL;
726  }
727 
728  if (bounds().isEmpty())
729  {
730  QFile f(filename);
731  if(f.exists())
732  {
733  bool b = f.remove();
734  return (b) ? Status::OK : Status::FAIL;
735  }
736  return Status::SAFE;
737  }
738  return Status::SAFE;
739 }
740 
741 void BitmapImage::clear()
742 {
743  mImage.reset(new QImage); // null image
744  mBounds = QRect(0, 0, 0, 0);
745  mMinBound = true;
746  modification();
747 }
748 
749 QRgb BitmapImage::constScanLine(int x, int y) const
750 {
751  QRgb result = qRgba(0, 0, 0, 0);
752  if (mBounds.contains(QPoint(x, y)))
753  {
754  result = *(reinterpret_cast<const QRgb*>(mImage->constScanLine(y - mBounds.top())) + x - mBounds.left());
755  }
756  return result;
757 }
758 
759 void BitmapImage::scanLine(int x, int y, QRgb color)
760 {
761  extend(QPoint(x, y));
762  if (mBounds.contains(QPoint(x, y)))
763  {
764  // Make sure color is premultiplied before calling
765  *(reinterpret_cast<QRgb*>(image()->scanLine(y - mBounds.top())) + x - mBounds.left()) =
766  qRgba(
767  qRed(color),
768  qGreen(color),
769  qBlue(color),
770  qAlpha(color));
771  }
772 }
773 
774 void BitmapImage::clear(QRect rectangle)
775 {
776  QRect clearRectangle = mBounds.intersected(rectangle);
777  clearRectangle.moveTopLeft(clearRectangle.topLeft() - mBounds.topLeft());
778 
780 
781  QPainter painter(image());
783  painter.fillRect(clearRectangle, QColor(0, 0, 0, 0));
784  painter.end();
785 
786  modification();
787 }
788 
803 bool BitmapImage::compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash<QRgb, bool> *cache)
804 {
805  // Handle trivial case
806  if (newColor == oldColor) return true;
807 
808  if(cache && cache->contains(newColor)) return cache->value(newColor);
809 
810  // Get Eulcidian distance between colors
811  // Not an accurate representation of human perception,
812  // but it's the best any image editing program ever does
813  int diffRed = static_cast<int>(qPow(qRed(oldColor) - qRed(newColor), 2));
814  int diffGreen = static_cast<int>(qPow(qGreen(oldColor) - qGreen(newColor), 2));
815  int diffBlue = static_cast<int>(qPow(qBlue(oldColor) - qBlue(newColor), 2));
816  // This may not be the best way to handle alpha since the other channels become less relevant as
817  // the alpha is reduces (ex. QColor(0,0,0,0) is the same as QColor(255,255,255,0))
818  int diffAlpha = static_cast<int>(qPow(qAlpha(oldColor) - qAlpha(newColor), 2));
819 
820  bool isSimilar = (diffRed + diffGreen + diffBlue + diffAlpha) <= tolerance;
821 
822  if(cache)
823  {
824  Q_ASSERT(cache->contains(isSimilar) ? isSimilar == (*cache)[newColor] : true);
825  (*cache)[newColor] = isSimilar;
826  }
827 
828  return isSimilar;
829 }
830 
831 // Flood fill
832 // ----- http://lodev.org/cgtutor/floodfill.html
833 void BitmapImage::floodFill(BitmapImage* targetImage,
834  QRect cameraRect,
835  QPoint point,
836  QRgb newColor,
837  int tolerance)
838 {
839  // If the point we are supposed to fill is outside the image and camera bounds, do nothing
840  if(!cameraRect.united(targetImage->bounds()).contains(point))
841  {
842  return;
843  }
844 
845  // Square tolerance for use with compareColor
846  tolerance = static_cast<int>(qPow(tolerance, 2));
847 
848  QRgb oldColor = targetImage->pixel(point);
849  oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
850 
851  // Preparations
852  QList<QPoint> queue; // queue all the pixels of the filled area (as they are found)
853 
854  BitmapImage* replaceImage = nullptr;
855  QPoint tempPoint;
856  QRgb newPlacedColor = 0;
858 
859  int xTemp = 0;
860  bool spanLeft = false;
861  bool spanRight = false;
862 
863  // Extend to size of Camera
864  targetImage->extend(cameraRect);
865  replaceImage = new BitmapImage(targetImage->mBounds, Qt::transparent);
866 
867  queue.append(point);
868  // Preparations END
869 
870  while (!queue.empty())
871  {
872  tempPoint = queue.takeFirst();
873 
874  point.setX(tempPoint.x());
875  point.setY(tempPoint.y());
876 
877  xTemp = point.x();
878 
879  newPlacedColor = replaceImage->constScanLine(xTemp, point.y());
880  while (xTemp >= targetImage->mBounds.left() &&
881  compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data())) xTemp--;
882  xTemp++;
883 
884  spanLeft = spanRight = false;
885  while (xTemp <= targetImage->mBounds.right() &&
886  compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data()) &&
887  newPlacedColor != newColor)
888  {
889 
890  // Set pixel color
891  replaceImage->scanLine(xTemp, point.y(), newColor);
892 
893  if (!spanLeft && (point.y() > targetImage->mBounds.top()) &&
894  compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
895  queue.append(QPoint(xTemp, point.y() - 1));
896  spanLeft = true;
897  }
898  else if (spanLeft && (point.y() > targetImage->mBounds.top()) &&
899  !compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
900  spanLeft = false;
901  }
902 
903  if (!spanRight && point.y() < targetImage->mBounds.bottom() &&
904  compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
905  queue.append(QPoint(xTemp, point.y() + 1));
906  spanRight = true;
907 
908  }
909  else if (spanRight && point.y() < targetImage->mBounds.bottom() &&
910  !compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
911  spanRight = false;
912  }
913 
914  Q_ASSERT(queue.count() < (targetImage->mBounds.width() * targetImage->mBounds.height()));
915  xTemp++;
916  }
917  }
918 
919  targetImage->paste(replaceImage);
920  targetImage->modification();
921  delete replaceImage;
922 }
uchar * scanLine(int i)
void setTransform(const QTransform &transform, bool combine)
QSize size() const const
QRect normalized() const const
QRect toRect() const const
bool end()
RadialGradientPattern
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setCompositionMode(QPainter::CompositionMode mode)
void setRenderHint(QPainter::RenderHint hint, bool on)
Format_ARGB32_Premultiplied
int right() const const
Qt::BrushStyle style() const const
qreal length() const const
void setPixel(int x, int y, uint index_or_rgb)
QPointF center() const const
QPainterPath::Element elementAt(int index) const const
qreal top() const const
int height() const const
bool isNull() const const
void drawLine(const QLineF &line)
QImage copy(const QRect &rectangle) const const
void setFocalPoint(const QPointF &focalPoint)
void moveTopLeft(const QPoint &position)
qreal left() const const
int x() const const
int y() const const
void setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm)
Updates the bounds after a drawImage operation with the composition mode cm.
bool mMinBound
Definition: bitmapimage.h:132
const QGradient * gradient() const const
qreal bottom() const const
void drawRect(const QRectF &rectangle)
QRgb pixel(int x, int y) const const
int count(const T &value) const const
void append(const T &value)
void drawPoint(const QPointF &position)
bool empty() const const
void fill(uint pixelValue)
int top() const const
void setPen(const QColor &color)
QRectF controlPointRect() const const
void drawEllipse(const QRectF &rectangle)
int left() const const
void setWidth(int width)
QRect rect() const const
void setBrush(const QBrush &brush)
QRect translated(int dx, int dy) const const
QRect intersected(const QRect &rectangle) const const
void autoCrop()
Removes any transparent borders by reducing the boundaries.
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
const T value(const Key &key) const const
qreal right() const const
void setCenter(const QPointF &center)
bool isEmpty() const const
QPointF focalPoint() const const
bool contains(const QRect &rectangle, bool proper) const const
int width() const const
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
void drawPath(const QPainterPath &path)
QPoint toPoint() const const
int width() const const
T takeFirst()
QRectF translated(qreal dx, qreal dy) const const
void setHeight(int height)
QImage transformed(const QMatrix &matrix, Qt::TransformationMode mode) const const
void setWorldMatrixEnabled(bool enable)
int bottom() const const
QPoint topLeft() const const
QSize size() const const
void setX(int x)
void setY(int y)
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
bool contains(const Key &key) const const
void updateBounds(QRect rectangle)
Update image bounds.
transparent
SmoothPixmapTransform
void setSize(const QSize &size)
static bool compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash< QRgb, bool > *cache)
Compare colors for the purposes of flood filling.
QRect united(const QRect &rectangle) const const
SmoothTransformation
QRect mapRect(const QRect &rectangle) const const
QRgb rgba() const const