17#include "bitmapimage.h"
23#include <QImageWriter>
24#include <QPainterPath>
29#include "tiledbuffer.h"
31BitmapImage::BitmapImage()
39 mEnableAutoCrop = a.mEnableAutoCrop;
40 mOpacity = a.mOpacity;
44BitmapImage::BitmapImage(
const QRect& rectangle,
const QColor& color)
52BitmapImage::BitmapImage(
const QPoint& topLeft,
const QImage& image)
59BitmapImage::BitmapImage(
const QPoint& topLeft,
const QString& path)
69BitmapImage::~BitmapImage()
73void BitmapImage::setImage(
QImage* img)
89 KeyFrame::operator=(a);
92 mOpacity = a.mOpacity;
103 const bool validKeyFrame = !fileName().
isEmpty();
104 if (validKeyFrame && !isModified())
109 Q_ASSERT(finfo.isAbsolute());
110 Q_ASSERT(QFile::exists(fileName()));
115 newFilePath =
QString(
"%1/temp-%2.%3")
116 .
arg(finfo.canonicalPath())
117 .
arg(uniqueString(12))
118 .
arg(finfo.suffix());
120 while (QFile::exists(newFilePath));
122 b->setFileName(newFilePath);
123 bool ok = QFile::copy(fileName(), newFilePath);
125 qDebug() <<
"COPY>" << fileName();
132 if (!fileName().isEmpty() && !
isLoaded())
142 if (isModified() ==
false && !fileName().isEmpty())
150 if (mImage.
isNull()) {
return false; }
155quint64 BitmapImage::memoryUsage()
159 return imageSize(mImage);
164void BitmapImage::paintImage(
QPainter& painter)
176QImage* BitmapImage::image()
199 if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
206 QImage* image2 = bitmapImage->image();
218 if(tiledBuffer->bounds().
width() <= 0 || tiledBuffer->bounds().
height() <= 0)
222 extend(tiledBuffer->bounds());
227 auto const tiles = tiledBuffer->tiles();
228 for (
const Tile* item : tiles) {
229 const QPixmap& tilePixmap = item->pixmap();
230 const QPoint& tilePos = item->pos();
238void BitmapImage::moveTopLeft(
QPoint point)
245void BitmapImage::transform(
QRect newBoundaries,
bool smoothTransform)
247 mBounds = newBoundaries;
256 painter.
drawImage(newBoundaries, *image());
265 Q_ASSERT(!selection.
isEmpty());
277 transformedImage = selectedPart.image()->
transformed(transform);
282BitmapImage BitmapImage::transformed(
QRect newBoundaries,
bool smoothTransform)
285 QPainter painter(transformedImage.image());
288 painter.
drawImage(newBoundaries, *image());
290 return transformedImage;
303 if (mBounds == newBoundaries)
return;
314 mBounds = newBoundaries;
320void BitmapImage::extend(
const QPoint &p)
328void BitmapImage::extend(
QRect rectangle)
341 if (!newImage.isNull())
348 mBounds = newBoundaries;
397 newBoundaries = mBounds;
406 newBoundaries = mBounds;
413 newBoundaries = mBounds.
united(sourceBounds);
435 if (!mEnableAutoCrop)
return;
437 if (mImage.
isNull())
return;
439 Q_ASSERT(mBounds.
size() == mImage.
size());
445 const int width = mImage.
width();
449 int relBottom = mBounds.
height() - 1;
453 while (isEmpty && relTop <= relBottom)
456 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(relTop));
457 for (
int col = 0; col < width; col++)
461 if (qAlpha(*cursor) != 0)
481 while (isEmpty && relBottom >= relTop)
484 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(relBottom));
485 for (
int col = 0; col < width; col++)
489 if(qAlpha(*cursor) != 0)
509 int relRight = mBounds.
width()-1;
512 int minLeft = mBounds.
width();
513 for (
int row = relTop; row <= relBottom; ++row)
515 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(row));
516 for (
int col = 0; col < minLeft; ++col)
518 if (qAlpha(*cursor) != 0)
530 for (
int row = relTop; row <= relBottom; ++row)
532 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(row)) + mBounds.
width() - 1;
533 for (
int col = mBounds.
width() - 1; col > minRight; --col)
535 if (qAlpha(*cursor) != 0)
545 if (relTop > relBottom || relLeft > relRight)
560QRgb BitmapImage::pixel(
int x,
int y)
562 return pixel(
QPoint(x, y));
565QRgb BitmapImage::pixel(
QPoint p)
567 QRgb result = qRgba(0, 0, 0, 0);
573void BitmapImage::setPixel(
int x,
int y, QRgb color)
575 setPixel(
QPoint(x, y), color);
578void BitmapImage::setPixel(
QPoint p, QRgb color)
588void BitmapImage::fillNonAlphaPixels(
const QRgb color)
590 if (mBounds.
isEmpty()) {
return; }
598 int width = 2 + pen.
width();
600 if (!image()->isNull())
614 int width = pen.
width();
640 int width = pen.
width();
665 int width = pen.
width();
670 if (!image()->isNull())
709BitmapImage* BitmapImage::scanToTransparent(
BitmapImage *img,
const int threshold,
const bool redEnabled,
const bool greenEnabled,
const bool blueEnabled)
711 Q_ASSERT(img !=
nullptr);
713 QRgb rgba = img->constScanLine(img->left(), img->top());
714 if (qAlpha(rgba) == 0)
717 for (
int x = img->left(); x <= img->right(); x++)
719 for (
int y = img->top(); y <= img->bottom(); y++)
721 rgba = img->constScanLine(x, y);
723 if (qAlpha(rgba) == 0)
726 const int grayValue = qGray(rgba);
727 const int redValue = qRed(rgba);
728 const int greenValue = qGreen(rgba);
729 const int blueValue = qBlue(rgba);
730 if (grayValue >= threshold)
732 img->scanLine(x, y, transp);
734 else if (redValue > greenValue + COLORDIFF &&
735 redValue > blueValue + COLORDIFF &&
736 redValue > grayValue + GRAYSCALEDIFF)
740 img->scanLine(x, y, redline);
744 img->scanLine(x, y, transp);
747 else if (greenValue > redValue + COLORDIFF &&
748 greenValue > blueValue + COLORDIFF &&
749 greenValue > grayValue + GRAYSCALEDIFF)
753 img->scanLine(x, y, greenline);
757 img->scanLine(x, y, transp);
760 else if (blueValue > redValue + COLORDIFF &&
761 blueValue > greenValue + COLORDIFF &&
762 blueValue > grayValue + GRAYSCALEDIFF)
766 img->scanLine(x, y, blueline);
770 img->scanLine(x, y, transp);
775 if (grayValue >= LOW_THRESHOLD)
777 const qreal factor =
static_cast<qreal
>(threshold - grayValue) /
static_cast<qreal
>(threshold - LOW_THRESHOLD);
778 img->scanLine(x , y, qRgba(0, 0, 0,
static_cast<int>(threshold * factor)));
782 img->scanLine(x , y, blackline);
794 dd <<
"BitmapImage::writeFile";
800 bool b = writer.write(mImage);
804 dd <<
QString(
" Error: %1 (Code %2)").
arg(writer.errorString()).
arg(
static_cast<int>(writer.error()));
805 return Status(Status::FAIL, dd);
809 if (bounds().isEmpty())
816 dd <<
" Error: Image is empty but unable to remove file.";
828void BitmapImage::clear()
831 mBounds =
QRect(0, 0, 0, 0);
836QRgb BitmapImage::constScanLine(
int x,
int y)
const
838 QRgb result = QRgb();
840 result = *(
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(y - mBounds.
top())) + x - mBounds.
left());
845void BitmapImage::scanLine(
int x,
int y, QRgb color)
851 *(
reinterpret_cast<QRgb*
>(image()->
scanLine(y - mBounds.
top())) + x - mBounds.
left()) = color;
854void BitmapImage::clear(
QRect rectangle)
869bool BitmapImage::floodFill(
BitmapImage** replaceImage,
871 const QRect& cameraRect,
873 const QRgb& fillColor,
875 const int expandValue)
878 const QRect& fillBounds = targetImage->mBounds.
adjusted(-1, -1, 1, 1);
879 QRect maxBounds = cameraRect.
united(fillBounds).
adjusted(-expandValue, -expandValue, expandValue, expandValue);
880 const int maxWidth = maxBounds.
width(), left = maxBounds.
left(), top = maxBounds.
top();
883 tolerance =
static_cast<int>(qPow(tolerance, 2));
886 bool *filledPixels = floodFillPoints(targetImage, maxBounds, point, tolerance, newBounds);
891 const QRect& expandRect = newBounds.
adjusted(-expandValue, -expandValue, expandValue, expandValue);
892 if (expandValue > 0) {
893 newBounds = expandRect;
895 if (!maxBounds.
contains(newBounds)) {
896 newBounds = maxBounds;
900 if (expandValue > 0) {
901 expandFill(filledPixels, translatedSearchBounds, maxBounds, expandValue);
907 for (
int y = translatedSearchBounds.
top(); y <= translatedSearchBounds.
bottom(); y++)
909 for (
int x = translatedSearchBounds.
left(); x <= translatedSearchBounds.
right(); x++)
911 const int index = y * maxWidth + x;
912 if (!filledPixels[index])
916 (*replaceImage)->scanLine(x + left, y + top, fillColor);
920 delete[] filledPixels;
927bool* BitmapImage::floodFillPoints(
const BitmapImage* targetImage,
928 const QRect& searchBounds,
933 QRgb oldColor = targetImage->constScanLine(point.
x(), point.
y());
934 oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
943 bool spanLeft =
false;
944 bool spanRight =
false;
949 bool *filledPixels =
new bool[searchBounds.
height()*searchBounds.
width()]{};
952 while (!queue.
empty())
956 point.
setX(tempPoint.
x());
957 point.
setY(tempPoint.
y());
961 int xCoord = xTemp - searchBounds.
left();
962 int yCoord = point.
y() - searchBounds.
top();
964 if (filledPixels[yCoord*searchBounds.
width()+xCoord])
continue;
966 while (xTemp >= searchBounds.
left() &&
967 compareColor(targetImage->constScanLine(xTemp, point.
y()), oldColor, tolerance, cache.data())) xTemp--;
970 spanLeft = spanRight =
false;
971 while (xTemp <= searchBounds.
right() &&
972 compareColor(targetImage->constScanLine(xTemp, point.
y()), oldColor, tolerance, cache.data()))
976 if (!blitBounds.contains(floodPoint)) {
977 blitBounds.extend(floodPoint);
980 xCoord = xTemp - searchBounds.
left();
982 filledPixels[yCoord*searchBounds.
width()+xCoord] =
true;
984 if (!spanLeft && (point.
y() > searchBounds.
top()) &&
985 compareColor(targetImage->constScanLine(xTemp, point.
y() - 1), oldColor, tolerance, cache.data())) {
989 else if (spanLeft && (point.
y() > searchBounds.
top()) &&
990 !
compareColor(targetImage->constScanLine(xTemp, point.
y() - 1), oldColor, tolerance, cache.data())) {
994 if (!spanRight && point.
y() < searchBounds.
bottom() &&
995 compareColor(targetImage->constScanLine(xTemp, point.
y() + 1), oldColor, tolerance, cache.data())) {
999 else if (spanRight && point.
y() < searchBounds.
bottom() &&
1000 !
compareColor(targetImage->constScanLine(xTemp, point.
y() + 1), oldColor, tolerance, cache.data())) {
1009 newBounds = blitBounds;
1011 return filledPixels;
1031 const int maxWidth = maxBounds.
width();
1032 const int length = maxBounds.
height() * maxBounds.
width();
1034 int* manhattanPoints =
new int[length]{};
1037 std::fill_n(manhattanPoints, length, searchBounds.
width()+searchBounds.
height());
1039 for (
int y = searchBounds.
top(); y <= searchBounds.
bottom(); y++)
1041 for (
int x = searchBounds.
left(); x <= searchBounds.
right(); x++)
1043 const int index = y*maxWidth+x;
1045 if (fillPixels[index]) {
1046 manhattanPoints[index] = 0;
1050 if (y > searchBounds.
top()) {
1052 manhattanPoints[index] = qMin(manhattanPoints[index],
1053 manhattanPoints[(y - 1) * maxWidth+x] + 1);
1055 int distance = manhattanPoints[index];
1056 if (distance <= expand) {
1057 fillPixels[index] =
true;
1060 if (x > searchBounds.
left()) {
1062 manhattanPoints[index] = qMin(manhattanPoints[index],
1063 manhattanPoints[y*maxWidth+(x - 1)] + 1);
1065 int distance = manhattanPoints[index];
1066 if (distance <= expand) {
1067 fillPixels[index] =
true;
1074 for (
int y = searchBounds.
bottom(); y >= searchBounds.
top(); y--)
1076 for (
int x = searchBounds.
right(); x >= searchBounds.
left(); x--)
1078 const int index = y*maxWidth+x;
1080 if (y + 1 < searchBounds.
bottom()) {
1081 manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[(y + 1)*maxWidth+x] + 1);
1083 int distance = manhattanPoints[index];
1084 if (distance <= expand) {
1085 fillPixels[index] =
true;
1088 if (x + 1 < searchBounds.
right()) {
1089 manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[y*maxWidth+(x + 1)] + 1);
1091 int distance = manhattanPoints[index];
1092 if (distance <= expand) {
1093 fillPixels[index] =
true;
1099 delete[] manhattanPoints;
void updateBounds(QRect rectangle)
Update image bounds.
void loadFile() override
Loads the backing image data from disk and into memory if it exists but hasn't been loaded yet.
bool isLoaded() const override
Checks whether the keyframe holds valid image data.
void setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm)
Updates the bounds after a drawImage operation with the composition mode cm.
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 autoCrop()
Removes any transparent borders by reducing the boundaries.
static bool compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash< QRgb, bool > *cache)
Compare colors for the purposes of flood filling.
void unloadFile() override
Unloads the image data from memory if there's valid backing data on disk and the latest state has bee...
const QGradient * gradient() const const
Qt::BrushStyle style() const const
const uchar * constScanLine(int i) const const
void fill(uint pixelValue)
bool isNull() const const
QRgb pixel(int x, int y) const const
void setPixel(int x, int y, uint index_or_rgb)
void append(const T &value)
int count(const T &value) const const
void drawEllipse(const QRectF &rectangle)
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
void drawLine(const QLineF &line)
void drawPath(const QPainterPath &path)
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
void drawPoint(const QPointF &position)
void drawRect(const QRectF &rectangle)
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setBrush(const QBrush &brush)
void setCompositionMode(QPainter::CompositionMode mode)
void setPen(const QColor &color)
void setRenderHint(QPainter::RenderHint hint, bool on)
void setWorldMatrixEnabled(bool enable)
QRectF controlPointRect() const const
QPainterPath::Element elementAt(int index) const const
qreal length() const const
QPoint toPoint() const const
QPointF center() const const
QPointF focalPoint() const const
void setCenter(const QPointF ¢er)
void setFocalPoint(const QPointF &focalPoint)
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
bool contains(const QRect &rectangle, bool proper) const const
QRect intersected(const QRect &rectangle) const const
bool isEmpty() const const
void moveTopLeft(const QPoint &position)
QRect normalized() const const
void setHeight(int height)
void setSize(const QSize &size)
QPoint topLeft() const const
QRect translated(int dx, int dy) const const
QRect united(const QRect &rectangle) const const
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
QRect toRect() const const
QRectF translated(qreal dx, qreal dy) const const
QString & append(QChar ch)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isEmpty() const const