Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed performance issue when tinting tiles from large tilesets #4080

Merged
merged 1 commit into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Scripting: Added `tiled.cursor` to create mouse cursor values
* Fixed saving/loading of custom properties set on worlds (#4025)
* Fixed crash when accessing a world through a symlink (#4042)
* Fixed performance issue when tinting tiles from large tilesets
* Fixed error reporting when exporting on the command-line (by Shuhei Nagasawa, #4015)
* Fixed minimum value of spinbox in Tile Animation Editor

Expand Down
51 changes: 36 additions & 15 deletions src/libtiled/maprenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,33 @@ using namespace Tiled;

struct TintedKey
{
const qint64 key;
const qint64 pixmapKey;
const QRect rect;
const QColor color;

bool operator==(const TintedKey &o) const
{
return key == o.key && color == o.color;
return pixmapKey == o.pixmapKey &&
rect == o.rect &&
color == o.color;
}
};

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
uint qHash(const TintedKey &key, uint seed) Q_DECL_NOTHROW
#else
size_t qHash(const TintedKey &key, size_t seed) Q_DECL_NOTHROW
#endif
{
auto h = ::qHash(key.key, seed);
auto h = ::qHash(key.pixmapKey, seed);
h = ::qHash(key.rect.topLeft(), h);
h = ::qHash(key.rect.bottomRight(), h);
h = ::qHash(key.color.rgba(), h);
return h;
}
#else
size_t qHash(const TintedKey &key, size_t seed) Q_DECL_NOTHROW
{
return qHashMulti(seed, key.pixmapKey, key.rect, key.color.rgba());
}
#endif

// Borrowed from qpixmapcache.cpp
static inline qsizetype cost(const QPixmap &pixmap)
Expand All @@ -80,19 +88,25 @@ static inline qsizetype cost(const QPixmap &pixmap)
return static_cast<qsizetype>(qBound(1LL, costKb, costMax));
}

static QPixmap tinted(const QPixmap &pixmap, const QColor &color)
static bool needsTint(const QColor &color)
{
return color.isValid() &&
color != QColor(255, 255, 255, 255);
}

static QPixmap tinted(const QPixmap &pixmap, const QRect &rect, const QColor &color)
{
if (!color.isValid() || color == QColor(255, 255, 255, 255) || pixmap.isNull())
if (pixmap.isNull() || !needsTint(color))
return pixmap;

// Cache for up to 100 MB of tinted pixmaps, since tinting is expensive
static QCache<TintedKey, QPixmap> cache { 100 * 1024 };

const TintedKey tintedKey { pixmap.cacheKey(), color };
const TintedKey tintedKey { pixmap.cacheKey(), rect, color };
if (auto cached = cache.object(tintedKey))
return *cached;

QPixmap resultImage = pixmap;
QPixmap resultImage = pixmap.copy(rect);
QPainter painter(&resultImage);

QColor fullOpacity = color;
Expand All @@ -104,7 +118,7 @@ static QPixmap tinted(const QPixmap &pixmap, const QColor &color)

// apply the original alpha to the final image
painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
painter.drawPixmap(0, 0, pixmap);
painter.drawPixmap(resultImage.rect(), pixmap, rect);

// apply the alpha of the tint color so that we can use it to make the image
// transparent instead of just increasing or decreasing the tint effect
Expand Down Expand Up @@ -171,7 +185,9 @@ void MapRenderer::drawImageLayer(QPainter *painter,
const QRectF &exposed) const
{
painter->save();
painter->setBrush(tinted(imageLayer->image(), imageLayer->effectiveTintColor()));
painter->setBrush(tinted(imageLayer->image(),
imageLayer->image().rect(),
imageLayer->effectiveTintColor()));
painter->setPen(Qt::NoPen);
if (exposed.isNull())
painter->drawRect(boundingRect(imageLayer));
Expand Down Expand Up @@ -431,10 +447,13 @@ void CellRenderer::render(const Cell &cell, const QPointF &screenPos, const QSiz
flush();

const QPixmap &image = tile->image();
const QRect imageRect = tile->imageRect();
QRect imageRect = tile->imageRect();
if (imageRect.isEmpty())
return;

if (needsTint(mTintColor))
imageRect.moveTopLeft(QPoint(0, 0));

const QPoint offset = tile->offset();
const QPointF sizeHalf { size.width() / 2, size.height() / 2 };

Expand Down Expand Up @@ -520,7 +539,7 @@ void CellRenderer::render(const Cell &cell, const QPointF &screenPos, const QSiz
fragment.width, fragment.height);

mPainter->setTransform(transform);
mPainter->drawPixmap(target, tinted(image, mTintColor), source);
mPainter->drawPixmap(target, tinted(image, tile->imageRect(), mTintColor), source);
mPainter->setTransform(oldTransform);

// A bit of a hack to still draw tile collision shapes when requested
Expand All @@ -545,7 +564,9 @@ void CellRenderer::flush()

mPainter->drawPixmapFragments(mFragments.constData(),
mFragments.size(),
tinted(mTile->image(), mTintColor));
tinted(mTile->image(),
mTile->imageRect(),
mTintColor));

if (mRenderer->flags().testFlag(ShowTileCollisionShapes)
&& mTile->objectGroup()
Expand Down