Pencil2D Animation
Download Community News Docs Contribute

core_lib/src/graphics/bitmap/bitmapimage.cpp Source File

  • Main Page
  • Related Pages
  • Classes
  • Files
  •  
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • graphics
  • bitmap
bitmapimage.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16*/
17#include "bitmapimage.h"
18
19#include <cmath>
20#include <QDebug>
21#include <QFile>
22#include <QFileInfo>
23#include <QPainterPath>
24#include "util.h"
25
26#include "blitrect.h"
27
28BitmapImage::BitmapImage()
29{
30}
31
32BitmapImage::BitmapImage(const BitmapImage& a) : KeyFrame(a)
33{
34 mBounds = a.mBounds;
35 mMinBound = a.mMinBound;
36 mEnableAutoCrop = a.mEnableAutoCrop;
37 mOpacity = a.mOpacity;
38 mImage = a.mImage;
39}
40
41BitmapImage::BitmapImage(const QRect& rectangle, const QColor& color)
42{
43 mBounds = rectangle;
44 mImage = QImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
45 mImage.fill(color.rgba());
46 mMinBound = false;
47}
48
49BitmapImage::BitmapImage(const QPoint& topLeft, const QImage& image)
50{
51 mBounds = QRect(topLeft, image.size());
52 mMinBound = true;
53 mImage = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
54}
55
56BitmapImage::BitmapImage(const QPoint& topLeft, const QString& path)
57{
58 setFileName(path);
59 mImage = QImage();
60
61 mBounds = QRect(topLeft, QSize(-1, 0));
62 mMinBound = true;
63 setModified(false);
64}
65
66BitmapImage::~BitmapImage()
67{
68}
69
70void BitmapImage::setImage(QImage* img)
71{
72 Q_ASSERT(img && img->format() == QImage::Format_ARGB32_Premultiplied);
73 mImage = *img;
74 mMinBound = false;
75
76 modification();
77}
78
79BitmapImage& BitmapImage::operator=(const BitmapImage& a)
80{
81 if (this == &a)
82 {
83 return *this; // a self-assignment
84 }
85
86 KeyFrame::operator=(a);
87 mBounds = a.mBounds;
88 mMinBound = a.mMinBound;
89 mOpacity = a.mOpacity;
90 mImage = a.mImage;
91 modification();
92 return *this;
93}
94
95BitmapImage* BitmapImage::clone() const
96{
97 BitmapImage* b = new BitmapImage(*this);
98 b->setFileName(""); // don't link to the file of the source bitmap image
99
100 const bool validKeyFrame = !fileName().isEmpty();
101 if (validKeyFrame && !isLoaded())
102 {
103 // This bitmapImage is temporarily unloaded.
104 // since it's not in the memory, we need to copy the linked png file to prevent data loss.
105 QFileInfo finfo(fileName());
106 Q_ASSERT(finfo.isAbsolute());
107 Q_ASSERT(QFile::exists(fileName()));
108
109 QString newFileName = QString("%1/%2-%3.%4")
110 .arg(finfo.canonicalPath())
111 .arg(finfo.completeBaseName())
112 .arg(uniqueString(12))
113 .arg(finfo.suffix());
114 b->setFileName(newFileName);
115
116 bool ok = QFile::copy(fileName(), newFileName);
117 Q_ASSERT(ok);
118 qDebug() << "COPY>" << fileName();
119 }
120 return b;
121}
122
123void BitmapImage::loadFile()
124{
125 if (!fileName().isEmpty() && !isLoaded())
126 {
127 mImage = QImage(fileName()).convertToFormat(QImage::Format_ARGB32_Premultiplied);
128 mBounds.setSize(mImage.size());
129 mMinBound = false;
130 }
131}
132
133void BitmapImage::unloadFile()
134{
135 if (isModified() == false)
136 {
137 mImage = QImage();
138 }
139}
140
141bool BitmapImage::isLoaded() const
142{
143 return mImage.width() == mBounds.width();
144}
145
146quint64 BitmapImage::memoryUsage()
147{
148 if (!mImage.isNull())
149 {
150 return imageSize(mImage);
151 }
152 return 0;
153}
154
155void BitmapImage::paintImage(QPainter& painter)
156{
157 painter.drawImage(mBounds.topLeft(), *image());
158}
159
160void BitmapImage::paintImage(QPainter& painter, QImage& image, QRect sourceRect, QRect destRect)
161{
162 painter.drawImage(QRect(mBounds.topLeft(), destRect.size()),
163 image,
164 sourceRect);
165}
166
167QImage* BitmapImage::image()
168{
169 loadFile();
170 return &mImage;
171}
172
173BitmapImage BitmapImage::copy()
174{
175 return BitmapImage(mBounds.topLeft(), *image());
176}
177
178BitmapImage BitmapImage::copy(QRect rectangle)
179{
180 if (rectangle.isEmpty() || mBounds.isEmpty()) return BitmapImage();
181
182 QRect intersection2 = rectangle.translated(-mBounds.topLeft());
183
184 BitmapImage result(rectangle.topLeft(), image()->copy(intersection2));
185 return result;
186}
187
188void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
189{
190 if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
191 {
192 return;
193 }
194
195 setCompositionModeBounds(bitmapImage, cm);
196
197 QImage* image2 = bitmapImage->image();
198
199 QPainter painter(image());
200 painter.setCompositionMode(cm);
201 painter.drawImage(bitmapImage->mBounds.topLeft() - mBounds.topLeft(), *image2);
202 painter.end();
203
204 modification();
205}
206
207void BitmapImage::moveTopLeft(QPoint point)
208{
209 mBounds.moveTopLeft(point);
210 // Size is unchanged so there is no need to update mBounds
211 modification();
212}
213
214void BitmapImage::transform(QRect newBoundaries, bool smoothTransform)
215{
216 mBounds = newBoundaries;
217 newBoundaries.moveTopLeft(QPoint(0, 0));
218 QImage newImage(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
219
220 QPainter painter(&newImage);
221 painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
222 painter.setCompositionMode(QPainter::CompositionMode_Source);
223 painter.fillRect(newImage.rect(), QColor(0, 0, 0, 0));
224 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
225 painter.drawImage(newBoundaries, *image());
226 painter.end();
227 mImage = newImage;
228
229 modification();
230}
231
232BitmapImage BitmapImage::transformed(QRect selection, QTransform transform, bool smoothTransform)
233{
234 Q_ASSERT(!selection.isEmpty());
235
236 BitmapImage selectedPart = copy(selection);
237
238 // Get the transformed image
239 QImage transformedImage;
240 if (smoothTransform)
241 {
242 transformedImage = selectedPart.image()->transformed(transform, Qt::SmoothTransformation);
243 }
244 else
245 {
246 transformedImage = selectedPart.image()->transformed(transform);
247 }
248 return BitmapImage(transform.mapRect(selection).normalized().topLeft(), transformedImage);
249}
250
251BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
252{
253 BitmapImage transformedImage(newBoundaries, QColor(0, 0, 0, 0));
254 QPainter painter(transformedImage.image());
255 painter.setRenderHint(QPainter::SmoothPixmapTransform, smoothTransform);
256 newBoundaries.moveTopLeft(QPoint(0, 0));
257 painter.drawImage(newBoundaries, *image());
258 painter.end();
259 return transformedImage;
260}
261
269void BitmapImage::updateBounds(QRect newBoundaries)
270{
271 // Check to make sure changes actually need to be made
272 if (mBounds == newBoundaries) return;
273
274 QImage newImage(newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
275 newImage.fill(Qt::transparent);
276 if (!newImage.isNull())
277 {
278 QPainter painter(&newImage);
279 painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), mImage);
280 painter.end();
281 }
282 mImage = newImage;
283 mBounds = newBoundaries;
284 mMinBound = false;
285
286 modification();
287}
288
289void BitmapImage::extend(const QPoint &p)
290{
291 if (!mBounds.contains(p))
292 {
293 extend(QRect(p, QSize(1, 1)));
294 }
295}
296
297void BitmapImage::extend(QRect rectangle)
298{
299 if (rectangle.width() <= 0) rectangle.setWidth(1);
300 if (rectangle.height() <= 0) rectangle.setHeight(1);
301 if (mBounds.contains(rectangle))
302 {
303 // Do nothing
304 }
305 else
306 {
307 QRect newBoundaries = mBounds.united(rectangle).normalized();
308 QImage newImage(newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
309 newImage.fill(Qt::transparent);
310 if (!newImage.isNull())
311 {
312 QPainter painter(&newImage);
313 painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *image());
314 painter.end();
315 }
316 mImage = newImage;
317 mBounds = newBoundaries;
318
319 modification();
320 }
321}
322
330void BitmapImage::setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm)
331{
332 if (source)
333 {
334 setCompositionModeBounds(source->mBounds, source->mMinBound, cm);
335 }
336}
337
357void BitmapImage::setCompositionModeBounds(QRect sourceBounds, bool isSourceMinBounds, QPainter::CompositionMode cm)
358{
359 QRect newBoundaries;
360 switch(cm)
361 {
362 case QPainter::CompositionMode_Destination:
363 case QPainter::CompositionMode_SourceAtop:
364 // The Destination and SourceAtop modes
365 // do not change the bounds from destination.
366 newBoundaries = mBounds;
367 // mMinBound remains the same
368 break;
369 case QPainter::CompositionMode_SourceIn:
370 case QPainter::CompositionMode_DestinationIn:
371 case QPainter::CompositionMode_Clear:
372 case QPainter::CompositionMode_DestinationOut:
373 // The bounds of the result of SourceIn, DestinationIn, Clear, and DestinationOut
374 // modes are no larger than the destination bounds
375 newBoundaries = mBounds;
376 mMinBound = false;
377 break;
378 default:
379 // If it's not one of the above cases, create a union of the two bounds.
380 // This contains the minimum bounds, if both the destination and source
381 // use their respective minimum bounds.
382 newBoundaries = mBounds.united(sourceBounds);
383 mMinBound = mMinBound && isSourceMinBounds;
384 }
385
386 updateBounds(newBoundaries);
387}
388
402void BitmapImage::autoCrop()
403{
404 if (!mEnableAutoCrop) return;
405 if (mBounds.isEmpty()) return; // Exit if current bounds are null
406 if (mImage.isNull()) return;
407
408 Q_ASSERT(mBounds.size() == mImage.size());
409
410 // Exit if already min bounded
411 if (mMinBound) return;
412
413 // Get image properties
414 const int width = mImage.width();
415
416 // Relative top and bottom row indices (inclusive)
417 int relTop = 0;
418 int relBottom = mBounds.height() - 1;
419
420 // Check top row
421 bool isEmpty = true; // Used to track if a non-transparent pixel has been found
422 while (isEmpty && relTop <= relBottom) // Loop through rows
423 {
424 // Point cursor to the first pixel in the current top row
425 const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relTop));
426 for (int col = 0; col < width; col++) // Loop through pixels in row
427 {
428 // If the pixel is not transparent
429 // (i.e. alpha channel > 0)
430 if (qAlpha(*cursor) != 0)
431 {
432 // We've found a non-transparent pixel in row relTop,
433 // so we can stop looking for one
434 isEmpty = false;
435 break;
436 }
437 // Move cursor to point to the next pixel in the row
438 cursor++;
439 }
440 if (isEmpty)
441 {
442 // If the row we just checked was empty, increase relTop
443 // to remove the empty row from the top of the bounding box
444 ++relTop;
445 }
446 }
447
448 // Check bottom row
449 isEmpty = true; // Reset isEmpty
450 while (isEmpty && relBottom >= relTop) // Loop through rows
451 {
452 // Point cursor to the first pixel in the current bottom row
453 const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relBottom));
454 for (int col = 0; col < width; col++) // Loop through pixels in row
455 {
456 // If the pixel is not transparent
457 // (i.e. alpha channel > 0)
458 if(qAlpha(*cursor) != 0)
459 {
460 // We've found a non-transparent pixel in row relBottom,
461 // so we can stop looking for one
462 isEmpty = false;
463 break;
464 }
465 // Move cursor to point to the next pixel in the row
466 ++cursor;
467 }
468 if (isEmpty)
469 {
470 // If the row we just checked was empty, decrease relBottom
471 // to remove the empty row from the bottom of the bounding box
472 --relBottom;
473 }
474 }
475
476 // Relative left and right column indices (inclusive)
477 int relLeft = 0;
478 int relRight = mBounds.width()-1;
479
480 // Check left row
481 isEmpty = (relBottom >= relTop); // Check left only when
482 while (isEmpty && relBottom >= relTop && relLeft <= relRight) // Loop through columns
483 {
484 // Point cursor to the pixel at row relTop and column relLeft
485 const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relTop)) + relLeft;
486 // Loop through pixels in column
487 // Note: we only need to loop from relTop to relBottom (inclusive)
488 // not the full image height, because rows 0 to relTop-1 and
489 // relBottom+1 to mBounds.height() have already been
490 // confirmed to contain only transparent pixels
491 for (int row = relTop; row <= relBottom; row++)
492 {
493 // If the pixel is not transparent
494 // (i.e. alpha channel > 0)
495 if(qAlpha(*cursor) != 0)
496 {
497 // We've found a non-transparent pixel in column relLeft,
498 // so we can stop looking for one
499 isEmpty = false;
500 break;
501 }
502 // Move cursor to point to next pixel in the column
503 // Increment by width because the data is in row-major order
504 cursor += width;
505 }
506 if (isEmpty)
507 {
508 // If the column we just checked was empty, increase relLeft
509 // to remove the empty column from the left of the bounding box
510 ++relLeft;
511 }
512 }
513
514 // Check right row
515 isEmpty = (relBottom >= relTop); // Reset isEmpty
516 while (isEmpty && relRight >= relLeft) // Loop through columns
517 {
518 // Point cursor to the pixel at row relTop and column relRight
519 const QRgb* cursor = reinterpret_cast<const QRgb*>(mImage.constScanLine(relTop)) + relRight;
520 // Loop through pixels in column
521 // Note: we only need to loop from relTop to relBottom (inclusive)
522 // not the full image height, because rows 0 to relTop-1 and
523 // relBottom+1 to mBounds.height()-1 have already been
524 // confirmed to contain only transparent pixels
525 for (int row = relTop; row <= relBottom; row++)
526 {
527 // If the pixel is not transparent
528 // (i.e. alpha channel > 0)
529 if(qAlpha(*cursor) != 0)
530 {
531 // We've found a non-transparent pixel in column relRight,
532 // so we can stop looking for one
533 isEmpty = false;
534 break;
535 }
536 // Move cursor to point to next pixel in the column
537 // Increment by width because the data is in row-major order
538 cursor += width;
539 }
540 if (isEmpty)
541 {
542 // If the column we just checked was empty, increase relRight
543 // to remove the empty column from the left of the bounding box
544 --relRight;
545 }
546 }
547
548 //qDebug() << "Original" << mBounds;
549 //qDebug() << "Autocrop" << relLeft << relTop << relRight - mBounds.width() + 1 << relBottom - mBounds.height() + 1;
550 // Update mBounds and mImage if necessary
551 updateBounds(mBounds.adjusted(relLeft, relTop, relRight - mBounds.width() + 1, relBottom - mBounds.height() + 1));
552
553 //qDebug() << "New bounds" << mBounds;
554
555 mMinBound = true;
556}
557
558QRgb BitmapImage::pixel(int x, int y)
559{
560 return pixel(QPoint(x, y));
561}
562
563QRgb BitmapImage::pixel(QPoint p)
564{
565 QRgb result = qRgba(0, 0, 0, 0); // black
566 if (mBounds.contains(p))
567 result = image()->pixel(p - mBounds.topLeft());
568 return result;
569}
570
571void BitmapImage::setPixel(int x, int y, QRgb color)
572{
573 setPixel(QPoint(x, y), color);
574}
575
576void BitmapImage::setPixel(QPoint p, QRgb color)
577{
578 setCompositionModeBounds(QRect(p, QSize(1,1)), true, QPainter::CompositionMode_SourceOver);
579 if (mBounds.contains(p))
580 {
581 image()->setPixel(p - mBounds.topLeft(), color);
582 }
583 modification();
584}
585
586void BitmapImage::fillNonAlphaPixels(const QRgb color)
587{
588 if (mBounds.isEmpty()) { return; }
589
590 BitmapImage fill(bounds(), color);
591 paste(&fill, QPainter::CompositionMode_SourceIn);
592}
593
594void BitmapImage::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing)
595{
596 int width = 2 + pen.width();
597 setCompositionModeBounds(QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width, -width, width, width), true, cm);
598 if (!image()->isNull())
599 {
600 QPainter painter(image());
601 painter.setCompositionMode(cm);
602 painter.setRenderHint(QPainter::Antialiasing, antialiasing);
603 painter.setPen(pen);
604 painter.drawLine(P1 - mBounds.topLeft(), P2 - mBounds.topLeft());
605 painter.end();
606 }
607 modification();
608}
609
610void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
611{
612 int width = pen.width();
613 setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
614 if (brush.style() == Qt::RadialGradientPattern)
615 {
616 QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
617 gradient->setCenter(gradient->center() - mBounds.topLeft());
618 gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
619 }
620 if (!image()->isNull())
621 {
622 QPainter painter(image());
623 painter.setCompositionMode(cm);
624 painter.setRenderHint(QPainter::Antialiasing, antialiasing);
625 painter.setPen(pen);
626 painter.setBrush(brush);
627 painter.drawRect(rectangle.translated(-mBounds.topLeft()));
628 painter.end();
629 }
630 modification();
631}
632
633void BitmapImage::drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
634{
635 int width = pen.width();
636 setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
637 if (brush.style() == Qt::RadialGradientPattern)
638 {
639 QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
640 gradient->setCenter(gradient->center() - mBounds.topLeft());
641 gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
642 }
643 if (!image()->isNull())
644 {
645 QPainter painter(image());
646
647 painter.setRenderHint(QPainter::Antialiasing, antialiasing);
648 painter.setPen(pen);
649 painter.setBrush(brush);
650 painter.setCompositionMode(cm);
651 painter.drawEllipse(rectangle.translated(-mBounds.topLeft()));
652 painter.end();
653 }
654 modification();
655}
656
657void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush,
658 QPainter::CompositionMode cm, bool antialiasing)
659{
660 int width = pen.width();
661 // qreal inc = 1.0 + width / 20.0;
662
663 setCompositionModeBounds(path.controlPointRect().adjusted(-width, -width, width, width).toRect(), true, cm);
664
665 if (!image()->isNull())
666 {
667 QPainter painter(image());
668 painter.setCompositionMode(cm);
669 painter.setRenderHint(QPainter::Antialiasing, antialiasing);
670 painter.setPen(pen);
671 painter.setBrush(brush);
672 painter.setTransform(QTransform().translate(-mBounds.left(), -mBounds.top()));
673 painter.setWorldMatrixEnabled(true);
674 if (path.length() > 0)
675 {
676 /*
677 for (int pt = 0; pt < path.elementCount() - 1; pt++)
678 {
679 qreal dx = path.elementAt(pt + 1).x - path.elementAt(pt).x;
680 qreal dy = path.elementAt(pt + 1).y - path.elementAt(pt).y;
681 qreal m = sqrt(dx*dx + dy*dy);
682 qreal factorx = dx / m;
683 qreal factory = dy / m;
684 for (float h = 0.f; h < m; h += inc)
685 {
686 qreal x = path.elementAt(pt).x + factorx * h;
687 qreal y = path.elementAt(pt).y + factory * h;
688 painter.drawPoint(QPointF(x, y));
689 }
690 }
691 */
692 painter.drawPath( path );
693 }
694 else
695 {
696 // forces drawing when points are coincident (mousedown)
697 painter.drawPoint(static_cast<int>(path.elementAt(0).x), static_cast<int>(path.elementAt(0).y));
698 }
699 painter.end();
700 }
701 modification();
702}
703
704Status BitmapImage::writeFile(const QString& filename)
705{
706 if (!mImage.isNull())
707 {
708 bool b = mImage.save(filename);
709 return (b) ? Status::OK : Status::FAIL;
710 }
711
712 if (bounds().isEmpty())
713 {
714 QFile f(filename);
715 if(f.exists())
716 {
717 bool b = f.remove();
718 return (b) ? Status::OK : Status::FAIL;
719 }
720 return Status::SAFE;
721 }
722 return Status::SAFE;
723}
724
725void BitmapImage::clear()
726{
727 mImage = QImage(); // null image
728 mBounds = QRect(0, 0, 0, 0);
729 mMinBound = true;
730 modification();
731}
732
733QRgb BitmapImage::constScanLine(int x, int y) const
734{
735 QRgb result = QRgb();
736 if (mBounds.contains(x, y)) {
737 result = *(reinterpret_cast<const QRgb*>(mImage.constScanLine(y - mBounds.top())) + x - mBounds.left());
738 }
739 return result;
740}
741
742void BitmapImage::scanLine(int x, int y, QRgb color)
743{
744 if (!mBounds.contains(x, y)) {
745 return;
746 }
747 // Make sure color is premultiplied before calling
748 *(reinterpret_cast<QRgb*>(image()->scanLine(y - mBounds.top())) + x - mBounds.left()) = color;
749}
750
751void BitmapImage::clear(QRect rectangle)
752{
753 QRect clearRectangle = mBounds.intersected(rectangle);
754 clearRectangle.moveTopLeft(clearRectangle.topLeft() - mBounds.topLeft());
755
756 setCompositionModeBounds(clearRectangle, true, QPainter::CompositionMode_Clear);
757
758 QPainter painter(image());
759 painter.setCompositionMode(QPainter::CompositionMode_Clear);
760 painter.fillRect(clearRectangle, QColor(0, 0, 0, 0));
761 painter.end();
762
763 modification();
764}
765
766bool BitmapImage::floodFill(BitmapImage** replaceImage,
767 const BitmapImage* targetImage,
768 const QRect& cameraRect,
769 const QPoint& point,
770 const QRgb& fillColor,
771 int tolerance,
772 const int expandValue)
773{
774 // Fill region must be 1 pixel larger than the target image to fill regions on the edge connected only by transparent pixels
775 const QRect& fillBounds = targetImage->mBounds;
776 QRect maxBounds = cameraRect.united(fillBounds).adjusted(-expandValue, -expandValue, expandValue, expandValue);
777 const int maxWidth = maxBounds.width(), left = maxBounds.left(), top = maxBounds.top();
778
779 // If the point we are supposed to fill is outside the max bounds, do nothing
780 if(!maxBounds.contains(point))
781 {
782 return false;
783 }
784
785 // Square tolerance for use with compareColor
786 tolerance = static_cast<int>(qPow(tolerance, 2));
787
788 QRect newBounds;
789 bool shouldFillBorder = false;
790 bool *filledPixels = floodFillPoints(targetImage, fillBounds, maxBounds, point, tolerance, newBounds, shouldFillBorder);
791
792 QRect translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());
793
794 if (shouldFillBorder)
795 {
796 for (int y = 0; y < maxBounds.height(); y++)
797 {
798 for (int x = 0; x < maxBounds.width(); x++)
799 {
800 if(!translatedSearchBounds.contains(x, y))
801 {
802 filledPixels[y*maxWidth+x] = true;
803 }
804 }
805 }
806 newBounds = maxBounds;
807 }
808
809 // The scanned bounds should take the expansion into account
810 const QRect& expandRect = newBounds.adjusted(-expandValue, -expandValue, expandValue, expandValue);
811 if (expandValue > 0) {
812 newBounds = expandRect;
813 }
814 if (!maxBounds.contains(newBounds)) {
815 newBounds = maxBounds;
816 }
817 translatedSearchBounds = newBounds.translated(-maxBounds.topLeft());
818
819 if (expandValue > 0) {
820 expandFill(filledPixels, translatedSearchBounds, maxBounds, expandValue);
821 }
822
823 *replaceImage = new BitmapImage(newBounds, Qt::transparent);
824
825 // Fill all the found pixels
826 for (int y = translatedSearchBounds.top(); y <= translatedSearchBounds.bottom(); y++)
827 {
828 for (int x = translatedSearchBounds.left(); x <= translatedSearchBounds.right(); x++)
829 {
830 const int index = y * maxWidth + x;
831 if (!filledPixels[index])
832 {
833 continue;
834 }
835 (*replaceImage)->scanLine(x + left, y + top, fillColor);
836 }
837 }
838
839 delete[] filledPixels;
840
841 return true;
842}
843
844// Flood filling based on this scanline algorithm
845// ----- http://lodev.org/cgtutor/floodfill.html
846bool* BitmapImage::floodFillPoints(const BitmapImage* targetImage,
847 QRect searchBounds,
848 const QRect& maxBounds,
849 QPoint point,
850 const int tolerance,
851 QRect& newBounds,
852 bool& fillBorder)
853{
854 QRgb oldColor = targetImage->constScanLine(point.x(), point.y());
855 oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
856 QRect borderBounds = searchBounds.intersected(maxBounds);
857 searchBounds = searchBounds.adjusted(-1, -1, 1, 1).intersected(maxBounds);
858
859 // Preparations
860 QList<QPoint> queue; // queue all the pixels of the filled area (as they are found)
861
862 QPoint tempPoint;
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 if (!searchBounds.contains(point))
870 {
871 // If point is outside the search area, move it anywhere in the 1px transparent border
872 point = searchBounds.topLeft();
873 }
874
875 queue.append(point);
876 // Preparations END
877
878 bool *filledPixels = new bool[maxBounds.height()*maxBounds.width()]{};
879
880 // True if the algorithm has attempted to fill a pixel outside the search bounds
881 bool checkOutside = false;
882
883 BlitRect blitBounds(point);
884 while (!queue.empty())
885 {
886 tempPoint = queue.takeFirst();
887
888 point.setX(tempPoint.x());
889 point.setY(tempPoint.y());
890
891 xTemp = point.x();
892
893 int xCoord = xTemp - maxBounds.left();
894 int yCoord = point.y() - maxBounds.top();
895
896 // In case we fill outside the searchBounds, expand the search area to the max.
897 if (!borderBounds.contains(point)) {
898 checkOutside = true;
899 }
900
901 if (filledPixels[yCoord*maxBounds.width()+xCoord]) continue;
902
903 while (xTemp >= searchBounds.left() &&
904 compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data())) xTemp--;
905 xTemp++;
906
907 spanLeft = spanRight = false;
908 while (xTemp <= searchBounds.right() &&
909 compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data()))
910 {
911
912 QPoint floodPoint = QPoint(xTemp, point.y());
913 if (!blitBounds.contains(floodPoint)) {
914 blitBounds.extend(floodPoint);
915 }
916
917 xCoord = xTemp - maxBounds.left();
918 // This pixel is what we're going to fill later
919 filledPixels[yCoord*maxBounds.width()+xCoord] = true;
920
921 if (!spanLeft && (point.y() > searchBounds.top()) &&
922 compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
923 queue.append(QPoint(xTemp, point.y() - 1));
924 spanLeft = true;
925 }
926 else if (spanLeft && (point.y() > searchBounds.top()) &&
927 !compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
928 spanLeft = false;
929 }
930
931 if (!spanRight && point.y() < searchBounds.bottom() &&
932 compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
933 queue.append(QPoint(xTemp, point.y() + 1));
934 spanRight = true;
935 }
936 else if (spanRight && point.y() < searchBounds.bottom() &&
937 !compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
938 spanRight = false;
939 }
940
941 Q_ASSERT(queue.count() < (maxBounds.width() * maxBounds.height()));
942 xTemp++;
943 }
944 }
945
946 fillBorder = checkOutside && compareColor(qRgba(0,0,0,0), oldColor, tolerance, cache.data());
947
948 newBounds = blitBounds;
949
950 return filledPixels;
951}
952
968void BitmapImage::expandFill(bool* fillPixels, const QRect& searchBounds, const QRect& maxBounds, int expand) {
969
970 const int maxWidth = maxBounds.width();
971 const int length = maxBounds.height() * maxBounds.width();
972
973 int* manhattanPoints = new int[length]{};
974
975 // Fill points with max length, this is important because otherwise the filled pixels will include a border of the expanded area
976 std::fill_n(manhattanPoints, length, searchBounds.width()+searchBounds.height());
977
978 for (int y = searchBounds.top(); y <= searchBounds.bottom(); y++)
979 {
980 for (int x = searchBounds.left(); x <= searchBounds.right(); x++)
981 {
982 const int index = y*maxWidth+x;
983
984 if (fillPixels[index]) {
985 manhattanPoints[index] = 0;
986 continue;
987 }
988
989 if (y > searchBounds.top()) {
990 // the value will be the num of pixels away from y - 1 of the next position
991 manhattanPoints[index] = qMin(manhattanPoints[index],
992 manhattanPoints[(y - 1) * maxWidth+x] + 1);
993
994 int distance = manhattanPoints[index];
995 if (distance <= expand) {
996 fillPixels[index] = true;
997 }
998 }
999 if (x > searchBounds.left()) {
1000 // the value will be the num of pixels away from x - 1 of the next position
1001 manhattanPoints[index] = qMin(manhattanPoints[index],
1002 manhattanPoints[y*maxWidth+(x - 1)] + 1);
1003
1004 int distance = manhattanPoints[index];
1005 if (distance <= expand) {
1006 fillPixels[index] = true;
1007 }
1008 }
1009 }
1010 }
1011
1012 // traverse from bottom right to top left
1013 for (int y = searchBounds.bottom(); y >= searchBounds.top(); y--)
1014 {
1015 for (int x = searchBounds.right(); x >= searchBounds.left(); x--)
1016 {
1017 const int index = y*maxWidth+x;
1018
1019 if (y + 1 < searchBounds.bottom()) {
1020 manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[(y + 1)*maxWidth+x] + 1);
1021
1022 int distance = manhattanPoints[index];
1023 if (distance <= expand) {
1024 fillPixels[index] = true;
1025 }
1026 }
1027 if (x + 1 < searchBounds.right()) {
1028 manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[y*maxWidth+(x + 1)] + 1);
1029
1030 int distance = manhattanPoints[index];
1031 if (distance <= expand) {
1032 fillPixels[index] = true;
1033 }
1034 }
1035 }
1036 }
1037
1038 delete[] manhattanPoints;
1039}
1040
BitmapImage
Definition: bitmapimage.h:27
BitmapImage::updateBounds
void updateBounds(QRect rectangle)
Update image bounds.
Definition: bitmapimage.cpp:269
BitmapImage::mMinBound
bool mMinBound
Definition: bitmapimage.h:179
BitmapImage::setCompositionModeBounds
void setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm)
Updates the bounds after a drawImage operation with the composition mode cm.
Definition: bitmapimage.cpp:330
BitmapImage::expandFill
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.
Definition: bitmapimage.cpp:968
BitmapImage::autoCrop
void autoCrop()
Removes any transparent borders by reducing the boundaries.
Definition: bitmapimage.cpp:402
BitmapImage::compareColor
static bool compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash< QRgb, bool > *cache)
Compare colors for the purposes of flood filling.
Definition: bitmapimage.h:138
BlitRect
Definition: blitrect.h:25
KeyFrame
Definition: keyframe.h:30
Status
Definition: pencilerror.h:40
QBrush
QBrush::gradient
const QGradient * gradient() const const
QBrush::style
Qt::BrushStyle style() const const
QColor
QColor::rgba
QRgb rgba() const const
QFile
QFile::copy
bool copy(const QString &newName)
QFile::exists
bool exists() const const
QFileInfo
QHash
QImage::transformed
QImage transformed(const QMatrix &matrix, Qt::TransformationMode mode) const const
QImage
QImage::Format_ARGB32_Premultiplied
Format_ARGB32_Premultiplied
QImage::constScanLine
const uchar * constScanLine(int i) const const
QImage::convertToFormat
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags) const &const
QImage::fill
void fill(uint pixelValue)
QImage::format
QImage::Format format() const const
QImage::isNull
bool isNull() const const
QImage::pixel
QRgb pixel(int x, int y) const const
QImage::save
bool save(const QString &fileName, const char *format, int quality) const const
QImage::scanLine
uchar * scanLine(int i)
QImage::setPixel
void setPixel(int x, int y, uint index_or_rgb)
QImage::size
QSize size() const const
QImage::width
int width() const const
QList
QList::append
void append(const T &value)
QList::count
int count(const T &value) const const
QList::empty
bool empty() const const
QList::takeFirst
T takeFirst()
QPainter
QPainter::CompositionMode
CompositionMode
QPainter::SmoothPixmapTransform
SmoothPixmapTransform
QPainter::drawEllipse
void drawEllipse(const QRectF &rectangle)
QPainter::drawImage
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
QPainter::drawLine
void drawLine(const QLineF &line)
QPainter::drawPath
void drawPath(const QPainterPath &path)
QPainter::drawPoint
void drawPoint(const QPointF &position)
QPainter::drawRect
void drawRect(const QRectF &rectangle)
QPainter::end
bool end()
QPainter::fillRect
void fillRect(const QRectF &rectangle, const QBrush &brush)
QPainter::setBrush
void setBrush(const QBrush &brush)
QPainter::setCompositionMode
void setCompositionMode(QPainter::CompositionMode mode)
QPainter::setPen
void setPen(const QColor &color)
QPainter::setRenderHint
void setRenderHint(QPainter::RenderHint hint, bool on)
QPainter::setTransform
void setTransform(const QTransform &transform, bool combine)
QPainter::setWorldMatrixEnabled
void setWorldMatrixEnabled(bool enable)
QPainterPath
QPainterPath::controlPointRect
QRectF controlPointRect() const const
QPainterPath::elementAt
QPainterPath::Element elementAt(int index) const const
QPainterPath::length
qreal length() const const
QPen
QPen::width
int width() const const
QPoint
QPoint::setX
void setX(int x)
QPoint::setY
void setY(int y)
QPoint::x
int x() const const
QPoint::y
int y() const const
QPointF
QPointF::toPoint
QPoint toPoint() const const
QRadialGradient
QRadialGradient::center
QPointF center() const const
QRadialGradient::focalPoint
QPointF focalPoint() const const
QRadialGradient::setCenter
void setCenter(const QPointF &center)
QRadialGradient::setFocalPoint
void setFocalPoint(const QPointF &focalPoint)
QRect
QRect::adjusted
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
QRect::bottom
int bottom() const const
QRect::contains
bool contains(const QRect &rectangle, bool proper) const const
QRect::height
int height() const const
QRect::intersected
QRect intersected(const QRect &rectangle) const const
QRect::isEmpty
bool isEmpty() const const
QRect::left
int left() const const
QRect::moveTopLeft
void moveTopLeft(const QPoint &position)
QRect::normalized
QRect normalized() const const
QRect::right
int right() const const
QRect::setHeight
void setHeight(int height)
QRect::setSize
void setSize(const QSize &size)
QRect::setWidth
void setWidth(int width)
QRect::size
QSize size() const const
QRect::top
int top() const const
QRect::topLeft
QPoint topLeft() const const
QRect::translated
QRect translated(int dx, int dy) const const
QRect::united
QRect united(const QRect &rectangle) const const
QRect::width
int width() const const
QRectF
QRectF::adjusted
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
QRectF::toRect
QRect toRect() const const
QRectF::translated
QRectF translated(qreal dx, qreal dy) const const
QScopedPointer
QSize
QString
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString::isEmpty
bool isEmpty() const const
Qt::RadialGradientPattern
RadialGradientPattern
Qt::transparent
transparent
Qt::SmoothTransformation
SmoothTransformation
QTransform
QTransform::mapRect
QRect mapRect(const QRect &rectangle) const const
Generated on Sun Sep 24 2023 19:39:34 for Pencil2D by doxygen 1.9.6 based on revision 1395c86cb17dafbb32de44cbabe1f4c58636468d