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();
130void BitmapImage::loadFile()
132 if (!fileName().isEmpty() && !isLoaded())
140void BitmapImage::unloadFile()
142 if (isModified() ==
false)
148bool BitmapImage::isLoaded()
const
153quint64 BitmapImage::memoryUsage()
157 return imageSize(mImage);
162void BitmapImage::paintImage(
QPainter& painter)
174QImage* BitmapImage::image()
197 if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
204 QImage* image2 = bitmapImage->image();
216 if(tiledBuffer->bounds().
width() <= 0 || tiledBuffer->bounds().
height() <= 0)
220 extend(tiledBuffer->bounds());
225 auto const tiles = tiledBuffer->tiles();
226 for (
const Tile* item : tiles) {
227 const QPixmap& tilePixmap = item->pixmap();
228 const QPoint& tilePos = item->pos();
236void BitmapImage::moveTopLeft(
QPoint point)
243void BitmapImage::transform(
QRect newBoundaries,
bool smoothTransform)
245 mBounds = newBoundaries;
254 painter.
drawImage(newBoundaries, *image());
263 Q_ASSERT(!selection.
isEmpty());
275 transformedImage = selectedPart.image()->
transformed(transform);
280BitmapImage BitmapImage::transformed(
QRect newBoundaries,
bool smoothTransform)
283 QPainter painter(transformedImage.image());
286 painter.
drawImage(newBoundaries, *image());
288 return transformedImage;
301 if (mBounds == newBoundaries)
return;
312 mBounds = newBoundaries;
318void BitmapImage::extend(
const QPoint &p)
326void BitmapImage::extend(
QRect rectangle)
339 if (!newImage.isNull())
346 mBounds = newBoundaries;
395 newBoundaries = mBounds;
404 newBoundaries = mBounds;
411 newBoundaries = mBounds.
united(sourceBounds);
433 if (!mEnableAutoCrop)
return;
435 if (mImage.
isNull())
return;
437 Q_ASSERT(mBounds.
size() == mImage.
size());
443 const int width = mImage.
width();
447 int relBottom = mBounds.
height() - 1;
451 while (isEmpty && relTop <= relBottom)
454 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(relTop));
455 for (
int col = 0; col < width; col++)
459 if (qAlpha(*cursor) != 0)
479 while (isEmpty && relBottom >= relTop)
482 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(relBottom));
483 for (
int col = 0; col < width; col++)
487 if(qAlpha(*cursor) != 0)
507 int relRight = mBounds.
width()-1;
510 int minLeft = mBounds.
width();
511 for (
int row = relTop; row <= relBottom; ++row)
513 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(row));
514 for (
int col = 0; col < minLeft; ++col)
516 if (qAlpha(*cursor) != 0)
528 for (
int row = relTop; row <= relBottom; ++row)
530 const QRgb* cursor =
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(row)) + mBounds.
width() - 1;
531 for (
int col = mBounds.
width() - 1; col > minRight; --col)
533 if (qAlpha(*cursor) != 0)
543 if (relTop > relBottom || relLeft > relRight)
558QRgb BitmapImage::pixel(
int x,
int y)
560 return pixel(
QPoint(x, y));
563QRgb BitmapImage::pixel(
QPoint p)
565 QRgb result = qRgba(0, 0, 0, 0);
571void BitmapImage::setPixel(
int x,
int y, QRgb color)
573 setPixel(
QPoint(x, y), color);
576void BitmapImage::setPixel(
QPoint p, QRgb color)
586void BitmapImage::fillNonAlphaPixels(
const QRgb color)
588 if (mBounds.
isEmpty()) {
return; }
596 int width = 2 + pen.
width();
598 if (!image()->isNull())
612 int width = pen.
width();
638 int width = pen.
width();
663 int width = pen.
width();
668 if (!image()->isNull())
707BitmapImage* BitmapImage::scanToTransparent(
BitmapImage *img,
const int threshold,
const bool redEnabled,
const bool greenEnabled,
const bool blueEnabled)
709 Q_ASSERT(img !=
nullptr);
711 QRgb rgba = img->constScanLine(img->left(), img->top());
712 if (qAlpha(rgba) == 0)
715 for (
int x = img->left(); x <= img->right(); x++)
717 for (
int y = img->top(); y <= img->bottom(); y++)
719 rgba = img->constScanLine(x, y);
721 if (qAlpha(rgba) == 0)
724 const int grayValue = qGray(rgba);
725 const int redValue = qRed(rgba);
726 const int greenValue = qGreen(rgba);
727 const int blueValue = qBlue(rgba);
728 if (grayValue >= threshold)
730 img->scanLine(x, y, transp);
732 else if (redValue > greenValue + COLORDIFF &&
733 redValue > blueValue + COLORDIFF &&
734 redValue > grayValue + GRAYSCALEDIFF)
738 img->scanLine(x, y, redline);
742 img->scanLine(x, y, transp);
745 else if (greenValue > redValue + COLORDIFF &&
746 greenValue > blueValue + COLORDIFF &&
747 greenValue > grayValue + GRAYSCALEDIFF)
751 img->scanLine(x, y, greenline);
755 img->scanLine(x, y, transp);
758 else if (blueValue > redValue + COLORDIFF &&
759 blueValue > greenValue + COLORDIFF &&
760 blueValue > grayValue + GRAYSCALEDIFF)
764 img->scanLine(x, y, blueline);
768 img->scanLine(x, y, transp);
773 if (grayValue >= LOW_THRESHOLD)
775 const qreal factor =
static_cast<qreal
>(threshold - grayValue) /
static_cast<qreal
>(threshold - LOW_THRESHOLD);
776 img->scanLine(x , y, qRgba(0, 0, 0,
static_cast<int>(threshold * factor)));
780 img->scanLine(x , y, blackline);
792 dd <<
"BitmapImage::writeFile";
798 bool b = writer.write(mImage);
802 dd <<
QString(
" Error: %1 (Code %2)").
arg(writer.errorString()).
arg(
static_cast<int>(writer.error()));
803 return Status(Status::FAIL, dd);
807 if (bounds().isEmpty())
814 dd <<
" Error: Image is empty but unable to remove file.";
826void BitmapImage::clear()
829 mBounds =
QRect(0, 0, 0, 0);
834QRgb BitmapImage::constScanLine(
int x,
int y)
const
836 QRgb result = QRgb();
838 result = *(
reinterpret_cast<const QRgb*
>(mImage.
constScanLine(y - mBounds.
top())) + x - mBounds.
left());
843void BitmapImage::scanLine(
int x,
int y, QRgb color)
849 *(
reinterpret_cast<QRgb*
>(image()->
scanLine(y - mBounds.
top())) + x - mBounds.
left()) = color;
852void BitmapImage::clear(
QRect rectangle)
867bool BitmapImage::floodFill(
BitmapImage** replaceImage,
869 const QRect& cameraRect,
871 const QRgb& fillColor,
873 const int expandValue)
876 const QRect& fillBounds = targetImage->mBounds.
adjusted(-1, -1, 1, 1);
877 QRect maxBounds = cameraRect.
united(fillBounds).
adjusted(-expandValue, -expandValue, expandValue, expandValue);
878 const int maxWidth = maxBounds.
width(), left = maxBounds.
left(), top = maxBounds.
top();
881 tolerance =
static_cast<int>(qPow(tolerance, 2));
884 bool *filledPixels = floodFillPoints(targetImage, maxBounds, point, tolerance, newBounds);
889 const QRect& expandRect = newBounds.
adjusted(-expandValue, -expandValue, expandValue, expandValue);
890 if (expandValue > 0) {
891 newBounds = expandRect;
893 if (!maxBounds.
contains(newBounds)) {
894 newBounds = maxBounds;
898 if (expandValue > 0) {
899 expandFill(filledPixels, translatedSearchBounds, maxBounds, expandValue);
905 for (
int y = translatedSearchBounds.
top(); y <= translatedSearchBounds.
bottom(); y++)
907 for (
int x = translatedSearchBounds.
left(); x <= translatedSearchBounds.
right(); x++)
909 const int index = y * maxWidth + x;
910 if (!filledPixels[index])
914 (*replaceImage)->scanLine(x + left, y + top, fillColor);
918 delete[] filledPixels;
925bool* BitmapImage::floodFillPoints(
const BitmapImage* targetImage,
926 const QRect& searchBounds,
931 QRgb oldColor = targetImage->constScanLine(point.
x(), point.
y());
932 oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
941 bool spanLeft =
false;
942 bool spanRight =
false;
947 bool *filledPixels =
new bool[searchBounds.
height()*searchBounds.
width()]{};
950 while (!queue.
empty())
954 point.
setX(tempPoint.
x());
955 point.
setY(tempPoint.
y());
959 int xCoord = xTemp - searchBounds.
left();
960 int yCoord = point.
y() - searchBounds.
top();
962 if (filledPixels[yCoord*searchBounds.
width()+xCoord])
continue;
964 while (xTemp >= searchBounds.
left() &&
965 compareColor(targetImage->constScanLine(xTemp, point.
y()), oldColor, tolerance, cache.data())) xTemp--;
968 spanLeft = spanRight =
false;
969 while (xTemp <= searchBounds.
right() &&
970 compareColor(targetImage->constScanLine(xTemp, point.
y()), oldColor, tolerance, cache.data()))
974 if (!blitBounds.contains(floodPoint)) {
975 blitBounds.extend(floodPoint);
978 xCoord = xTemp - searchBounds.
left();
980 filledPixels[yCoord*searchBounds.
width()+xCoord] =
true;
982 if (!spanLeft && (point.
y() > searchBounds.
top()) &&
983 compareColor(targetImage->constScanLine(xTemp, point.
y() - 1), oldColor, tolerance, cache.data())) {
987 else if (spanLeft && (point.
y() > searchBounds.
top()) &&
988 !
compareColor(targetImage->constScanLine(xTemp, point.
y() - 1), oldColor, tolerance, cache.data())) {
992 if (!spanRight && point.
y() < searchBounds.
bottom() &&
993 compareColor(targetImage->constScanLine(xTemp, point.
y() + 1), oldColor, tolerance, cache.data())) {
997 else if (spanRight && point.
y() < searchBounds.
bottom() &&
998 !
compareColor(targetImage->constScanLine(xTemp, point.
y() + 1), oldColor, tolerance, cache.data())) {
1007 newBounds = blitBounds;
1009 return filledPixels;
1029 const int maxWidth = maxBounds.
width();
1030 const int length = maxBounds.
height() * maxBounds.
width();
1032 int* manhattanPoints =
new int[length]{};
1035 std::fill_n(manhattanPoints, length, searchBounds.
width()+searchBounds.
height());
1037 for (
int y = searchBounds.
top(); y <= searchBounds.
bottom(); y++)
1039 for (
int x = searchBounds.
left(); x <= searchBounds.
right(); x++)
1041 const int index = y*maxWidth+x;
1043 if (fillPixels[index]) {
1044 manhattanPoints[index] = 0;
1048 if (y > searchBounds.
top()) {
1050 manhattanPoints[index] = qMin(manhattanPoints[index],
1051 manhattanPoints[(y - 1) * maxWidth+x] + 1);
1053 int distance = manhattanPoints[index];
1054 if (distance <= expand) {
1055 fillPixels[index] =
true;
1058 if (x > searchBounds.
left()) {
1060 manhattanPoints[index] = qMin(manhattanPoints[index],
1061 manhattanPoints[y*maxWidth+(x - 1)] + 1);
1063 int distance = manhattanPoints[index];
1064 if (distance <= expand) {
1065 fillPixels[index] =
true;
1072 for (
int y = searchBounds.
bottom(); y >= searchBounds.
top(); y--)
1074 for (
int x = searchBounds.
right(); x >= searchBounds.
left(); x--)
1076 const int index = y*maxWidth+x;
1078 if (y + 1 < searchBounds.
bottom()) {
1079 manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[(y + 1)*maxWidth+x] + 1);
1081 int distance = manhattanPoints[index];
1082 if (distance <= expand) {
1083 fillPixels[index] =
true;
1086 if (x + 1 < searchBounds.
right()) {
1087 manhattanPoints[index] = qMin(manhattanPoints[index], manhattanPoints[y*maxWidth+(x + 1)] + 1);
1089 int distance = manhattanPoints[index];
1090 if (distance <= expand) {
1091 fillPixels[index] =
true;
1097 delete[] manhattanPoints;
void updateBounds(QRect rectangle)
Update image bounds.
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.
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