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 <QFileInfo>
24 #include <QPainterPath>
25 #include "util.h"
26 
27 #include "blitrect.h"
28 
29 BitmapImage::BitmapImage()
30 {
31 }
32 
33 BitmapImage::BitmapImage(const BitmapImage& a) : KeyFrame(a)
34 {
35  mBounds = a.mBounds;
36  mMinBound = a.mMinBound;
37  mEnableAutoCrop = a.mEnableAutoCrop;
38  mOpacity = a.mOpacity;
39  mImage = a.mImage;
40 }
41 
42 BitmapImage::BitmapImage(const QRect& rectangle, const QColor& color)
43 {
44  mBounds = rectangle;
45  mImage = QImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
46  mImage.fill(color.rgba());
47  mMinBound = false;
48 }
49 
50 BitmapImage::BitmapImage(const QPoint& topLeft, const QImage& image)
51 {
52  mBounds = QRect(topLeft, image.size());
53  mMinBound = true;
54  mImage = image;
55 }
56 
57 BitmapImage::BitmapImage(const QPoint& topLeft, const QString& path)
58 {
59  setFileName(path);
60  mImage = QImage();
61 
62  mBounds = QRect(topLeft, QSize(-1, 0));
63  mMinBound = true;
64  setModified(false);
65 }
66 
67 BitmapImage::~BitmapImage()
68 {
69 }
70 
71 void BitmapImage::setImage(QImage* img)
72 {
73  Q_ASSERT(img);
74  mImage = *img;
75  mMinBound = false;
76 
77  modification();
78 }
79 
80 BitmapImage& BitmapImage::operator=(const BitmapImage& a)
81 {
82  if (this == &a)
83  {
84  return *this; // a self-assignment
85  }
86 
87  KeyFrame::operator=(a);
88  mBounds = a.mBounds;
89  mMinBound = a.mMinBound;
90  mOpacity = a.mOpacity;
91  mImage = a.mImage;
92  modification();
93  return *this;
94 }
95 
96 BitmapImage* BitmapImage::clone() const
97 {
98  BitmapImage* b = new BitmapImage(*this);
99  b->setFileName(""); // don't link to the file of the source bitmap image
100 
101  const bool validKeyFrame = !fileName().isEmpty();
102  if (validKeyFrame && !isLoaded())
103  {
104  // This bitmapImage is temporarily unloaded.
105  // since it's not in the memory, we need to copy the linked png file to prevent data loss.
106  QFileInfo finfo(fileName());
107  Q_ASSERT(finfo.isAbsolute());
108  Q_ASSERT(QFile::exists(fileName()));
109 
110  QString newFileName = QString("%1/%2-%3.%4")
111  .arg(finfo.canonicalPath())
112  .arg(finfo.completeBaseName())
113  .arg(uniqueString(12))
114  .arg(finfo.suffix());
115  b->setFileName(newFileName);
116 
117  bool ok = QFile::copy(fileName(), newFileName);
118  Q_ASSERT(ok);
119  qDebug() << "COPY>" << fileName();
120  }
121  return b;
122 }
123 
124 void BitmapImage::loadFile()
125 {
126  if (!fileName().isEmpty() && !isLoaded())
127  {
128  mImage = QImage(fileName());
129  mBounds.setSize(mImage.size());
130  mMinBound = false;
131  }
132 }
133 
134 void BitmapImage::unloadFile()
135 {
136  if (isModified() == false)
137  {
138  mImage = QImage();
139  }
140 }
141 
142 bool BitmapImage::isLoaded() const
143 {
144  return mImage.width() == mBounds.width();
145 }
146 
147 quint64 BitmapImage::memoryUsage()
148 {
149  if (!mImage.isNull())
150  {
151  return imageSize(mImage);
152  }
153  return 0;
154 }
155 
156 void BitmapImage::paintImage(QPainter& painter)
157 {
158  painter.drawImage(mBounds.topLeft(), *image());
159 }
160 
161 void BitmapImage::paintImage(QPainter& painter, QImage& image, QRect sourceRect, QRect destRect)
162 {
163  painter.drawImage(QRect(mBounds.topLeft(), destRect.size()),
164  image,
165  sourceRect);
166 }
167 
168 QImage* BitmapImage::image()
169 {
170  loadFile();
171  return &mImage;
172 }
173 
174 BitmapImage BitmapImage::copy()
175 {
176  return BitmapImage(mBounds.topLeft(), *image());
177 }
178 
179 BitmapImage BitmapImage::copy(QRect rectangle)
180 {
181  if (rectangle.isEmpty() || mBounds.isEmpty()) return BitmapImage();
182 
183  QRect intersection2 = rectangle.translated(-mBounds.topLeft());
184 
185  // If the region goes out of bounds, make sure the image is formatted in ARGB
186  // so that the area beyond the image bounds is transparent.
187  if (!mBounds.contains(rectangle) && !image()->hasAlphaChannel())
188  {
189  mImage = mImage.convertToFormat(QImage::Format_ARGB32);
190  }
191 
192  BitmapImage result(rectangle.topLeft(), image()->copy(intersection2));
193  return result;
194 }
195 
196 void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
197 {
198  if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
199  {
200  return;
201  }
202 
203  setCompositionModeBounds(bitmapImage, cm);
204 
205  QImage* image2 = bitmapImage->image();
206 
207  QPainter painter(image());
208  painter.setCompositionMode(cm);
209  painter.drawImage(bitmapImage->mBounds.topLeft() - mBounds.topLeft(), *image2);
210  painter.end();
211 
212  modification();
213 }
214 
215 void BitmapImage::moveTopLeft(QPoint point)
216 {
217  mBounds.moveTopLeft(point);
218  // Size is unchanged so there is no need to update mBounds
219  modification();
220 }
221 
222 void BitmapImage::transform(QRect newBoundaries, bool smoothTransform)
223 {
224  mBounds = newBoundaries;
225  newBoundaries.moveTopLeft(QPoint(0, 0));
226  QImage newImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
227 
228  QPainter painter(&newImage);
229  painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
231  painter.fillRect(newImage.rect(), QColor(0, 0, 0, 0));
233  painter.drawImage(newBoundaries, *image());
234  painter.end();
235  mImage = newImage;
236 
237  modification();
238 }
239 
240 BitmapImage BitmapImage::transformed(QRect selection, QTransform transform, bool smoothTransform)
241 {
242  Q_ASSERT(!selection.isEmpty());
243 
244  BitmapImage selectedPart = copy(selection);
245 
246  // Get the transformed image
247  QImage transformedImage;
248  if (smoothTransform)
249  {
250  transformedImage = selectedPart.image()->transformed(transform, Qt::SmoothTransformation);
251  }
252  else
253  {
254  transformedImage = selectedPart.image()->transformed(transform);
255  }
256  return BitmapImage(transform.mapRect(selection).normalized().topLeft(), transformedImage);
257 }
258 
259 BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
260 {
261  BitmapImage transformedImage(newBoundaries, QColor(0, 0, 0, 0));
262  QPainter painter(transformedImage.image());
263  painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
264  newBoundaries.moveTopLeft(QPoint(0, 0));
265  painter.drawImage(newBoundaries, *image());
266  painter.end();
267  return transformedImage;
268 }
269 
277 void BitmapImage::updateBounds(QRect newBoundaries)
278 {
279  // Check to make sure changes actually need to be made
280  if (mBounds == newBoundaries) return;
281 
282  QImage newImage(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(), mImage);
288  painter.end();
289  }
290  mImage = newImage;
291  mBounds = newBoundaries;
292  mMinBound = false;
293 
294  modification();
295 }
296 
297 void BitmapImage::extend(const QPoint &p)
298 {
299  if (!mBounds.contains(p))
300  {
301  extend(QRect(p, QSize(1, 1)));
302  }
303 }
304 
305 void BitmapImage::extend(QRect rectangle)
306 {
307  if (rectangle.width() <= 0) rectangle.setWidth(1);
308  if (rectangle.height() <= 0) rectangle.setHeight(1);
309  if (mBounds.contains(rectangle))
310  {
311  // Do nothing
312  }
313  else
314  {
315  QRect newBoundaries = mBounds.united(rectangle).normalized();
316  QImage newImage(newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
317  newImage.fill(Qt::transparent);
318  if (!newImage.isNull())
319  {
320  QPainter painter(&newImage);
321  painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *image());
322  painter.end();
323  }
324  mImage = newImage;
325  mBounds = newBoundaries;
326 
327  modification();
328  }
329 }
330 
339 {
340  if (source)
341  {
342  setCompositionModeBounds(source->mBounds, source->mMinBound, cm);
343  }
344 }
345 
365 void BitmapImage::setCompositionModeBounds(QRect sourceBounds, bool isSourceMinBounds, QPainter::CompositionMode cm)
366 {
367  QRect newBoundaries;
368  switch(cm)
369  {
372  // The Destination and SourceAtop modes
373  // do not change the bounds from destination.
374  newBoundaries = mBounds;
375  // mMinBound remains the same
376  break;
381  // The bounds of the result of SourceIn, DestinationIn, Clear, and DestinationOut
382  // modes are no larger than the destination bounds
383  newBoundaries = mBounds;
384  mMinBound = false;
385  break;
386  default:
387  // If it's not one of the above cases, create a union of the two bounds.
388  // This contains the minimum bounds, if both the destination and source
389  // use their respective minimum bounds.
390  newBoundaries = mBounds.united(sourceBounds);
391  mMinBound = mMinBound && isSourceMinBounds;
392  }
393 
394  updateBounds(newBoundaries);
395 }
396 
411 {
412  if (!mEnableAutoCrop) return;
413  if (mBounds.isEmpty()) return; // Exit if current bounds are null
414  if (mImage.isNull()) return;
415 
416  Q_ASSERT(mBounds.size() == mImage.size());
417 
418  // Exit if already min bounded
419  if (mMinBound) return;
420 
421  // Get image properties
422  const int width = mImage.width();
423 
424  // Relative top and bottom row indices (inclusive)
425  int relTop = 0;
426  int relBottom = mBounds.height() - 1;
427 
428  // Check top row
429  bool isEmpty = true; // Used to track if a non-transparent pixel has been found
430  while (isEmpty && relTop <= relBottom) // Loop through rows
431  {
432  // Point cursor to the first pixel in the current top row
433  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relTop));
434  for (int col = 0; col < width; col++) // Loop through pixels in row
435  {
436  // If the pixel is not transparent
437  // (i.e. alpha channel > 0)
438  if (qAlpha(*cursor) != 0)
439  {
440  // We've found a non-transparent pixel in row relTop,
441  // so we can stop looking for one
442  isEmpty = false;
443  break;
444  }
445  // Move cursor to point to the next pixel in the row
446  cursor++;
447  }
448  if (isEmpty)
449  {
450  // If the row we just checked was empty, increase relTop
451  // to remove the empty row from the top of the bounding box
452  ++relTop;
453  }
454  }
455 
456  // Check bottom row
457  isEmpty = true; // Reset isEmpty
458  while (isEmpty && relBottom >= relTop) // Loop through rows
459  {
460  // Point cursor to the first pixel in the current bottom row
461  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relBottom));
462  for (int col = 0; col < width; col++) // Loop through pixels in row
463  {
464  // If the pixel is not transparent
465  // (i.e. alpha channel > 0)
466  if(qAlpha(*cursor) != 0)
467  {
468  // We've found a non-transparent pixel in row relBottom,
469  // so we can stop looking for one
470  isEmpty = false;
471  break;
472  }
473  // Move cursor to point to the next pixel in the row
474  ++cursor;
475  }
476  if (isEmpty)
477  {
478  // If the row we just checked was empty, decrease relBottom
479  // to remove the empty row from the bottom of the bounding box
480  --relBottom;
481  }
482  }
483 
484  // Relative left and right column indices (inclusive)
485  int relLeft = 0;
486  int relRight = mBounds.width()-1;
487 
488  // Check left row
489  isEmpty = (relBottom >= relTop); // Check left only when
490  while (isEmpty && relBottom >= relTop && relLeft <= relRight) // Loop through columns
491  {
492  // Point cursor to the pixel at row relTop and column relLeft
493  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relTop)) + relLeft;
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() 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 relLeft,
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 relLeft
517  // to remove the empty column from the left of the bounding box
518  ++relLeft;
519  }
520  }
521 
522  // Check right row
523  isEmpty = (relBottom >= relTop); // Reset isEmpty
524  while (isEmpty && relRight >= relLeft) // Loop through columns
525  {
526  // Point cursor to the pixel at row relTop and column relRight
527  const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relTop)) + relRight;
528  // Loop through pixels in column
529  // Note: we only need to loop from relTop to relBottom (inclusive)
530  // not the full image height, because rows 0 to relTop-1 and
531  // relBottom+1 to mBounds.height()-1 have already been
532  // confirmed to contain only transparent pixels
533  for (int row = relTop; row <= relBottom; row++)
534  {
535  // If the pixel is not transparent
536  // (i.e. alpha channel > 0)
537  if(qAlpha(*cursor) != 0)
538  {
539  // We've found a non-transparent pixel in column relRight,
540  // so we can stop looking for one
541  isEmpty = false;
542  break;
543  }
544  // Move cursor to point to next pixel in the column
545  // Increment by width because the data is in row-major order
546  cursor += width;
547  }
548  if (isEmpty)
549  {
550  // If the column we just checked was empty, increase relRight
551  // to remove the empty column from the left of the bounding box
552  --relRight;
553  }
554  }
555 
556  //qDebug() << "Original" << mBounds;
557  //qDebug() << "Autocrop" << relLeft << relTop << relRight - mBounds.width() + 1 << relBottom - mBounds.height() + 1;
558  // Update mBounds and mImage if necessary
559  updateBounds(mBounds.adjusted(relLeft, relTop, relRight - mBounds.width() + 1, relBottom - mBounds.height() + 1));
560 
561  //qDebug() << "New bounds" << mBounds;
562 
563  mMinBound = true;
564 }
565 
566 QRgb BitmapImage::pixel(int x, int y)
567 {
568  return pixel(QPoint(x, y));
569 }
570 
571 QRgb BitmapImage::pixel(QPoint p)
572 {
573  QRgb result = qRgba(0, 0, 0, 0); // black
574  if (mBounds.contains(p))
575  result = image()->pixel(p - mBounds.topLeft());
576  return result;
577 }
578 
579 void BitmapImage::setPixel(int x, int y, QRgb color)
580 {
581  setPixel(QPoint(x, y), color);
582 }
583 
584 void BitmapImage::setPixel(QPoint p, QRgb color)
585 {
587  if (mBounds.contains(p))
588  {
589  image()->setPixel(p - mBounds.topLeft(), color);
590  }
591  modification();
592 }
593 
594 void BitmapImage::fillNonAlphaPixels(const QRgb color)
595 {
596  if (mBounds.isEmpty()) { return; }
597 
598  BitmapImage fill(bounds(), color);
600 }
601 
602 void BitmapImage::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing)
603 {
604  int width = 2 + pen.width();
605  setCompositionModeBounds(QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width, -width, width, width), true, cm);
606  if (!image()->isNull())
607  {
608  QPainter painter(image());
609  painter.setCompositionMode(cm);
610  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
611  painter.setPen(pen);
612  painter.drawLine(P1 - mBounds.topLeft(), P2 - mBounds.topLeft());
613  painter.end();
614  }
615  modification();
616 }
617 
618 void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
619 {
620  int width = pen.width();
621  setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
622  if (brush.style() == Qt::RadialGradientPattern)
623  {
624  QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
625  gradient->setCenter(gradient->center() - mBounds.topLeft());
626  gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
627  }
628  if (!image()->isNull())
629  {
630  QPainter painter(image());
631  painter.setCompositionMode(cm);
632  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
633  painter.setPen(pen);
634  painter.setBrush(brush);
635  painter.drawRect(rectangle.translated(-mBounds.topLeft()));
636  painter.end();
637  }
638  modification();
639 }
640 
641 void BitmapImage::drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
642 {
643  int width = pen.width();
644  setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
645  if (brush.style() == Qt::RadialGradientPattern)
646  {
647  QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
648  gradient->setCenter(gradient->center() - mBounds.topLeft());
649  gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
650  }
651  if (!image()->isNull())
652  {
653  QPainter painter(image());
654 
655  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
656  painter.setPen(pen);
657  painter.setBrush(brush);
658  painter.setCompositionMode(cm);
659  painter.drawEllipse(rectangle.translated(-mBounds.topLeft()));
660  painter.end();
661  }
662  modification();
663 }
664 
665 void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush,
666  QPainter::CompositionMode cm, bool antialiasing)
667 {
668  int width = pen.width();
669  // qreal inc = 1.0 + width / 20.0;
670 
671  setCompositionModeBounds(path.controlPointRect().adjusted(-width, -width, width, width).toRect(), true, cm);
672 
673  if (!image()->isNull())
674  {
675  QPainter painter(image());
676  painter.setCompositionMode(cm);
677  painter.setRenderHint(QPainter::Antialiasing, antialiasing);
678  painter.setPen(pen);
679  painter.setBrush(brush);
680  painter.setTransform(QTransform().translate(-mBounds.left(), -mBounds.top()));
681  painter.setWorldMatrixEnabled(true);
682  if (path.length() > 0)
683  {
684  /*
685  for (int pt = 0; pt < path.elementCount() - 1; pt++)
686  {
687  qreal dx = path.elementAt(pt + 1).x - path.elementAt(pt).x;
688  qreal dy = path.elementAt(pt + 1).y - path.elementAt(pt).y;
689  qreal m = sqrt(dx*dx + dy*dy);
690  qreal factorx = dx / m;
691  qreal factory = dy / m;
692  for (float h = 0.f; h < m; h += inc)
693  {
694  qreal x = path.elementAt(pt).x + factorx * h;
695  qreal y = path.elementAt(pt).y + factory * h;
696  painter.drawPoint(QPointF(x, y));
697  }
698  }
699  */
700  painter.drawPath( path );
701  }
702  else
703  {
704  // forces drawing when points are coincident (mousedown)
705  painter.drawPoint(static_cast<int>(path.elementAt(0).x), static_cast<int>(path.elementAt(0).y));
706  }
707  painter.end();
708  }
709  modification();
710 }
711 
712 Status BitmapImage::writeFile(const QString& filename)
713 {
714  if (!mImage.isNull())
715  {
716  bool b = mImage.save(filename);
717  return (b) ? Status::OK : Status::FAIL;
718  }
719 
720  if (bounds().isEmpty())
721  {
722  QFile f(filename);
723  if(f.exists())
724  {
725  bool b = f.remove();
726  return (b) ? Status::OK : Status::FAIL;
727  }
728  return Status::SAFE;
729  }
730  return Status::SAFE;
731 }
732 
733 void BitmapImage::clear()
734 {
735  mImage = QImage(); // null image
736  mBounds = QRect(0, 0, 0, 0);
737  mMinBound = true;
738  modification();
739 }
740 
741 QRgb BitmapImage::constScanLine(int x, int y) const
742 {
743  QRgb result = QRgb();
744  if (mBounds.contains(x, y)) {
745  result = *(reinterpret_cast<const QRgb*>(mImage.constScanLine(y - mBounds.top())) + x - mBounds.left());
746  }
747  return result;
748 }
749 
750 void BitmapImage::scanLine(int x, int y, QRgb color)
751 {
752  if (!mBounds.contains(x, y)) {
753  return;
754  }
755  // Make sure color is premultiplied before calling
756  *(reinterpret_cast<QRgb*>(image()->scanLine(y - mBounds.top())) + x - mBounds.left()) = color;
757 }
758 
759 void BitmapImage::clear(QRect rectangle)
760 {
761  QRect clearRectangle = mBounds.intersected(rectangle);
762  clearRectangle.moveTopLeft(clearRectangle.topLeft() - mBounds.topLeft());
763 
765 
766  QPainter painter(image());
768  painter.fillRect(clearRectangle, QColor(0, 0, 0, 0));
769  painter.end();
770 
771  modification();
772 }
773 
788 bool BitmapImage::compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash<QRgb, bool> *cache)
789 {
790  // Handle trivial case
791  if (newColor == oldColor) return true;
792 
793  if(cache && cache->contains(newColor)) return cache->value(newColor);
794 
795  // Get Eulcidian distance between colors
796  // Not an accurate representation of human perception,
797  // but it's the best any image editing program ever does
798  int diffRed = static_cast<int>(qPow(qRed(oldColor) - qRed(newColor), 2));
799  int diffGreen = static_cast<int>(qPow(qGreen(oldColor) - qGreen(newColor), 2));
800  int diffBlue = static_cast<int>(qPow(qBlue(oldColor) - qBlue(newColor), 2));
801  // This may not be the best way to handle alpha since the other channels become less relevant as
802  // the alpha is reduces (ex. QColor(0,0,0,0) is the same as QColor(255,255,255,0))
803  int diffAlpha = static_cast<int>(qPow(qAlpha(oldColor) - qAlpha(newColor), 2));
804 
805  bool isSimilar = (diffRed + diffGreen + diffBlue + diffAlpha) <= tolerance;
806 
807  if(cache)
808  {
809  Q_ASSERT(cache->contains(isSimilar) ? isSimilar == (*cache)[newColor] : true);
810  (*cache)[newColor] = isSimilar;
811  }
812 
813  return isSimilar;
814 }
815 
816 bool BitmapImage::floodFill(BitmapImage** replaceImage,
817  const BitmapImage* targetImage,
818  const QRect& cameraRect,
819  const QPoint& point,
820  const QRgb& fillColor,
821  int tolerance,
822  const int expandValue)
823 {
824  // Fill region must be 1 pixel larger than the target image to fill regions on the edge connected only by transparent pixels
825  const QRect& fillBounds = targetImage->mBounds;
826  QRect maxBounds = cameraRect.united(fillBounds).adjusted(-expandValue, -expandValue, expandValue, expandValue);
827  const int maxWidth = maxBounds.width(), left = maxBounds.left(), top = maxBounds.top();
828 
829  // If the point we are supposed to fill is outside the max bounds, do nothing
830  if(!maxBounds.contains(point))
831  {
832  return false;
833  }
834 
835  // Square tolerance for use with compareColor
836  tolerance = static_cast<int>(qPow(tolerance, 2));
837 
838  QRect newBounds;
839  bool shouldFillBorder = false;
840  bool *filledPixels = floodFillPoints(targetImage, fillBounds, maxBounds, point, tolerance, newBounds, shouldFillBorder);
841 
842  QRect translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());
843 
844  if (shouldFillBorder)
845  {
846  for (int y = 0; y < maxBounds.height(); y++)
847  {
848  for (int x = 0; x < maxBounds.width(); x++)
849  {
850  if(!translatedSearchBounds.contains(x, y))
851  {
852  filledPixels[y*maxWidth+x] = true;
853  }
854  }
855  }
856  newBounds = maxBounds;
857  }
858 
859  // The scanned bounds should take the expansion into account
860  const QRect& expandRect = newBounds.adjusted(-expandValue, -expandValue, expandValue, expandValue);
861  if (expandValue > 0) {
862  newBounds = expandRect;
863  }
864  if (!maxBounds.contains(newBounds)) {
865  newBounds = maxBounds;
866  }
867  translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());
868 
869  if (expandValue > 0) {
870  expandFill(filledPixels, translatedSearchBounds, maxBounds, expandValue);
871  }
872 
873  *replaceImage = new BitmapImage(newBounds, Qt::transparent);
874 
875  // Fill all the found pixels
876  for (int y = translatedSearchBounds.top(); y <= translatedSearchBounds.bottom(); y++)
877  {
878  for (int x = translatedSearchBounds.left(); x <= translatedSearchBounds.right(); x++)
879  {
880  const int index = y * maxWidth + x;
881  if (!filledPixels[index])
882  {
883  continue;
884  }
885  (*replaceImage)->scanLine(x + left, y + top, fillColor);
886  }
887  }
888 
889  delete[] filledPixels;
890 
891  return true;
892 }
893 
894 // Flood filling based on this scanline algorithm
895 // ----- http://lodev.org/cgtutor/floodfill.html
896 bool* BitmapImage::floodFillPoints(const BitmapImage* targetImage,
897  QRect searchBounds,
898  const QRect& maxBounds,
899  QPoint point,
900  const int tolerance,
901  QRect& newBounds,
902  bool& fillBorder)
903 {
904  QRgb oldColor = targetImage->constScanLine(point.x(), point.y());
905  oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
906  QRect borderBounds = searchBounds.intersected(maxBounds);
907  searchBounds = searchBounds.adjusted(-1, -1, 1, 1).intersected(maxBounds);
908 
909  // Preparations
910  QList<QPoint> queue; // queue all the pixels of the filled area (as they are found)
911 
912  QPoint tempPoint;
914 
915  int xTemp = 0;
916  bool spanLeft = false;
917  bool spanRight = false;
918 
919  if (!searchBounds.contains(point))
920  {
921  // If point is outside the search area, move it anywhere in the 1px transparent border
922  point = searchBounds.topLeft();
923  }
924 
925  queue.append(point);
926  // Preparations END
927 
928  bool *filledPixels = new bool[maxBounds.height()*maxBounds.width()]{};
929 
930  // True if the algorithm has attempted to fill a pixel outside the search bounds
931  bool checkOutside = false;
932 
933  BlitRect blitBounds(point);
934  while (!queue.empty())
935  {
936  tempPoint = queue.takeFirst();
937 
938  point.setX(tempPoint.x());
939  point.setY(tempPoint.y());
940 
941  xTemp = point.x();
942 
943  int xCoord = xTemp - maxBounds.left();
944  int yCoord = point.y() - maxBounds.top();
945 
946  // In case we fill outside the searchBounds, expand the search area to the max.
947  if (!borderBounds.contains(point)) {
948  checkOutside = true;
949  }
950 
951  if (filledPixels[yCoord*maxBounds.width()+xCoord]) continue;
952 
953  while (xTemp >= searchBounds.left() &&
954  compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data())) xTemp--;
955  xTemp++;
956 
957  spanLeft = spanRight = false;
958  while (xTemp <= searchBounds.right() &&
959  compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data()))
960  {
961 
962  QPoint floodPoint = QPoint(xTemp, point.y());
963  if (!blitBounds.contains(floodPoint)) {
964  blitBounds.extend(floodPoint);
965  }
966 
967  xCoord = xTemp - maxBounds.left();
968  // This pixel is what we're going to fill later
969  filledPixels[yCoord*maxBounds.width()+xCoord] = true;
970 
971  if (!spanLeft && (point.y() > searchBounds.top()) &&
972  compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
973  queue.append(QPoint(xTemp, point.y() - 1));
974  spanLeft = true;
975  }
976  else if (spanLeft && (point.y() > searchBounds.top()) &&
977  !compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
978  spanLeft = false;
979  }
980 
981  if (!spanRight && point.y() < searchBounds.bottom() &&
982  compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
983  queue.append(QPoint(xTemp, point.y() + 1));
984  spanRight = true;
985  }
986  else if (spanRight && point.y() < searchBounds.bottom() &&
987  !compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
988  spanRight = false;
989  }
990 
991  Q_ASSERT(queue.count() < (maxBounds.width() * maxBounds.height()));
992  xTemp++;
993  }
994  }
995 
996  fillBorder = checkOutside && compareColor(qRgba(0,0,0,0), oldColor, tolerance, cache.data());
997 
998  newBounds = blitBounds;
999 
1000  return filledPixels;
1001 }
1002 
1018 void BitmapImage::expandFill(bool* fillPixels, const QRect& searchBounds, const QRect& maxBounds, int expand) {
1019 
1020  const int maxWidth = maxBounds.width();
1021  const int length = maxBounds.height() * maxBounds.width();
1022 
1023  int* manhattanPoints = new int[length]{};
1024 
1025  // Fill points with max length, this is important because otherwise the filled pixels will include a border of the expanded area
1026  std::fill_n(manhattanPoints, length, searchBounds.width()+searchBounds.height());
1027 
1028  for (int y = searchBounds.top(); y <= searchBounds.bottom(); y++)
1029  {
1030  for (int x = searchBounds.left(); x <= searchBounds.right(); x++)
1031  {
1032  const int index = y*maxWidth+x;
1033 
1034  if (fillPixels[index]) {
1035  manhattanPoints[index] = 0;
1036  continue;
1037  }
1038 
1039  if (y > searchBounds.top()) {
1040  // the value will be the num of pixels away from y - 1 of the next position
1041  manhattanPoints[index] = qMin(manhattanPoints[index],
1042  manhattanPoints[(y - 1) * maxWidth+x] + 1);
1043 
1044  int distance = manhattanPoints[index];
1045  if (distance <= expand) {
1046  fillPixels[index] = true;
1047  }
1048  }
1049  if (x > searchBounds.left()) {
1050  // the value will be the num of pixels away from x - 1 of the next position
1051  manhattanPoints[index] = qMin(manhattanPoints[index],
1052  manhattanPoints[y*maxWidth+(x - 1)] + 1);
1053 
1054  int distance = manhattanPoints[index];
1055  if (distance <= expand) {
1056  fillPixels[index] = true;
1057  }
1058  }
1059  }
1060  }
1061 
1062  // traverse from bottom right to top left
1063  for (int y = searchBounds.bottom(); y >= searchBounds.top(); y--)
1064  {
1065  for (int x = searchBounds.right(); x >= searchBounds.left(); x--)
1066  {
1067  const int index = y*maxWidth+x;
1068 
1069  if (y + 1 < searchBounds.bottom()) {
1070  manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[(y + 1)*maxWidth+x] + 1);
1071 
1072  int distance = manhattanPoints[index];
1073  if (distance <= expand) {
1074  fillPixels[index] = true;
1075  }
1076  }
1077  if (x + 1 < searchBounds.right()) {
1078  manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[y*maxWidth+(x + 1)] + 1);
1079 
1080  int distance = manhattanPoints[index];
1081  if (distance <= expand) {
1082  fillPixels[index] = true;
1083  }
1084  }
1085  }
1086  }
1087 
1088  delete[] manhattanPoints;
1089 }
1090 
uchar * scanLine(int i)
void setTransform(const QTransform &transform, bool combine)
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags) const &const
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)
const uchar * constScanLine(int i) const const
static void expandFill(bool *fillPixels, const QRect &searchBounds, const QRect &maxBounds, int expand)
Finds all pixels closest to the input color and applies the input color to the image.
void setRenderHint(QPainter::RenderHint hint, bool on)
Format_ARGB32_Premultiplied
int right() const const
Qt::BrushStyle style() const const
qreal length() const const
bool save(const QString &fileName, const char *format, int quality) const const
void setPixel(int x, int y, uint index_or_rgb)
QPointF center() const const
bool hasAlphaChannel() const const
QPainterPath::Element elementAt(int index) const const
int height() const const
bool isNull() const const
bool exists() const const
void drawLine(const QLineF &line)
QImage copy(const QRect &rectangle) const const
void setFocalPoint(const QPointF &focalPoint)
void moveTopLeft(const QPoint &position)
bool copy(const QString &newName)
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.
const QGradient * gradient() 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
int width() const const
void drawEllipse(const QRectF &rectangle)
int left() const const
void setWidth(int width)
bool isEmpty() 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
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
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) 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)
QRect bool mMinBound
Definition: bitmapimage.h:136
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