Enforce tile/metatile limits on load, fix tile selector performance

This commit is contained in:
GriffinR 2025-07-17 16:28:51 -04:00
parent 70aeda9adb
commit dc4b1ef93a
11 changed files with 176 additions and 112 deletions

View File

@ -29,6 +29,7 @@ public:
static constexpr int pixelWidth() { return 8; }
static constexpr int pixelHeight() { return 8; }
static constexpr QSize pixelSize() { return QSize(Tile::pixelWidth(), Tile::pixelHeight()); }
static constexpr int sizeInBytes() { return sizeof(uint16_t); }
};
inline bool operator==(const Tile &a, const Tile &b) {

View File

@ -30,10 +30,8 @@ public:
QString metatile_attrs_label;
QString metatile_attrs_path;
QString tilesImagePath;
QImage tilesImage;
QStringList palettePaths;
QList<QImage> tiles;
QHash<int, QString> metatileLabels;
QList<QList<QRgb>> palettes;
QList<QList<QRgb>> palettePreviews;
@ -77,15 +75,24 @@ public:
void setMetatiles(const QList<Metatile*> &metatiles);
void addMetatile(Metatile* metatile);
QList<Metatile*> metatiles() const { return m_metatiles; }
const QList<Metatile*> &metatiles() const { return m_metatiles; }
Metatile* metatileAt(unsigned int i) const { return m_metatiles.at(i); }
void clearMetatiles();
void resizeMetatiles(int newNumMetatiles);
int numMetatiles() const { return m_metatiles.length(); }
int maxMetatiles() const;
int numTiles() const { return m_tiles.length(); }
int maxTiles() const;
QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); }
private:
QList<Metatile*> m_metatiles;
QList<QImage> m_tiles;
QImage m_tilesImage;
bool m_hasUnsavedTilesImage = false;
};

View File

@ -255,10 +255,13 @@ public:
static QMargins getMetatileViewDistance();
static int getNumTilesPrimary() { return num_tiles_primary; }
static int getNumTilesTotal() { return num_tiles_total; }
static int getNumTilesSecondary() { return getNumTilesTotal() - getNumTilesPrimary(); }
static int getNumMetatilesPrimary() { return num_metatiles_primary; }
static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; }
static int getNumMetatilesSecondary() { return getNumMetatilesTotal() - getNumMetatilesPrimary(); }
static int getNumPalettesPrimary(){ return num_pals_primary; }
static int getNumPalettesTotal() { return num_pals_total; }
static int getNumPalettesSecondary() { return getNumPalettesTotal() - getNumPalettesPrimary(); }
static QString getEmptyMapsecName();
static QString getMapGroupPrefix();

View File

@ -43,6 +43,7 @@ protected:
void hoverLeaveEvent(QGraphicsSceneHoverEvent*);
private:
QPixmap basePixmap;
bool externalSelection;
int externalSelectionWidth;
int externalSelectionHeight;
@ -63,7 +64,7 @@ private:
QList<QRgb> getCurPaletteTable();
QList<Tile> buildSelectedTiles(int, int, QList<Tile>);
QImage buildImage(int tileIdStart, int numTiles);
void updateBasePixmap();
void drawUnused();
signals:

View File

@ -20,15 +20,15 @@ Tileset::Tileset(const Tileset &other)
metatile_attrs_label(other.metatile_attrs_label),
metatile_attrs_path(other.metatile_attrs_path),
tilesImagePath(other.tilesImagePath),
tilesImage(other.tilesImage.copy()),
palettePaths(other.palettePaths),
metatileLabels(other.metatileLabels),
palettes(other.palettes),
palettePreviews(other.palettePreviews),
m_tilesImage(other.m_tilesImage.copy()),
m_hasUnsavedTilesImage(other.m_hasUnsavedTilesImage)
{
for (auto tile : other.tiles) {
tiles.append(tile.copy());
for (auto tile : other.m_tiles) {
m_tiles.append(tile.copy());
}
for (auto *metatile : other.m_metatiles) {
@ -46,15 +46,15 @@ Tileset &Tileset::operator=(const Tileset &other) {
metatile_attrs_label = other.metatile_attrs_label;
metatile_attrs_path = other.metatile_attrs_path;
tilesImagePath = other.tilesImagePath;
tilesImage = other.tilesImage.copy();
m_tilesImage = other.m_tilesImage.copy();
palettePaths = other.palettePaths;
metatileLabels = other.metatileLabels;
palettes = other.palettes;
palettePreviews = other.palettePreviews;
tiles.clear();
for (auto tile : other.tiles) {
tiles.append(tile.copy());
m_tiles.clear();
for (auto tile : other.m_tiles) {
m_tiles.append(tile.copy());
}
clearMetatiles();
@ -94,6 +94,14 @@ void Tileset::resizeMetatiles(int newNumMetatiles) {
}
}
int Tileset::maxMetatiles() const {
return this->is_secondary ? Project::getNumMetatilesSecondary() : Project::getNumMetatilesPrimary();
}
int Tileset::maxTiles() const {
return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary();
}
Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
if (tileId < Project::getNumTilesPrimary()) {
return primaryTileset;
@ -407,17 +415,25 @@ QHash<int, QString> Tileset::getHeaderMemberMap(bool usingAsm)
bool Tileset::loadMetatiles() {
clearMetatiles();
QFile metatiles_file(this->metatiles_path);
if (!metatiles_file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(metatiles_file.errorString()));
QFile file(this->metatiles_path);
if (!file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(file.errorString()));
return false;
}
QByteArray data = metatiles_file.readAll();
QByteArray data = file.readAll();
int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
int bytesPerMetatile = 2 * tilesPerMetatile;
int num_metatiles = data.length() / bytesPerMetatile;
for (int i = 0; i < num_metatiles; i++) {
int bytesPerMetatile = Tile::sizeInBytes() * tilesPerMetatile;
int numMetatiles = data.length() / bytesPerMetatile;
if (numMetatiles > maxMetatiles()) {
logWarn(QString("%1 metatile count %2 exceeds limit of %3. Additional metatiles will be ignored.")
.arg(this->name)
.arg(numMetatiles)
.arg(maxMetatiles()));
numMetatiles = maxMetatiles();
}
for (int i = 0; i < numMetatiles; i++) {
auto metatile = new Metatile;
int index = i * bytesPerMetatile;
for (int j = 0; j < tilesPerMetatile; j++) {
@ -431,9 +447,9 @@ bool Tileset::loadMetatiles() {
}
bool Tileset::saveMetatiles() {
QFile metatiles_file(this->metatiles_path);
if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(metatiles_file.errorString()));
QFile file(this->metatiles_path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(file.errorString()));
return false;
}
@ -441,31 +457,40 @@ bool Tileset::saveMetatiles() {
int numTiles = projectConfig.getNumTilesInMetatile();
for (const auto &metatile : m_metatiles) {
for (int i = 0; i < numTiles; i++) {
uint16_t tile = metatile->tiles.at(i).rawValue();
uint16_t tile = metatile->tiles.value(i).rawValue();
data.append(static_cast<char>(tile));
data.append(static_cast<char>(tile >> 8));
}
}
metatiles_file.write(data);
file.write(data);
return true;
}
bool Tileset::loadMetatileAttributes() {
QFile attrs_file(this->metatile_attrs_path);
if (!attrs_file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString()));
QFile file(this->metatile_attrs_path);
if (!file.open(QIODevice::ReadOnly)) {
logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(file.errorString()));
return false;
}
QByteArray data = attrs_file.readAll();
QByteArray data = file.readAll();
int attrSize = projectConfig.metatileAttributesSize;
int numMetatiles = m_metatiles.length();
int numMetatileAttrs = data.length() / attrSize;
if (numMetatiles != numMetatileAttrs) {
logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(numMetatiles).arg(numMetatileAttrs).arg(this->name));
if (numMetatileAttrs > numMetatiles) {
logWarn(QString("%1 metatile attributes count %2 exceeds metatile count of %3. Additional attributes will be ignored.")
.arg(this->name)
.arg(numMetatileAttrs)
.arg(numMetatiles));
numMetatileAttrs = numMetatiles;
} else if (numMetatileAttrs < numMetatiles) {
logWarn(QString("%1 metatile attributes count %2 is fewer than the metatile count of %3. Missing attributes will default to 0.")
.arg(this->name)
.arg(numMetatileAttrs)
.arg(numMetatiles));
}
for (int i = 0; i < qMin(numMetatiles, numMetatileAttrs); i++) {
for (int i = 0; i < numMetatileAttrs; i++) {
uint32_t attributes = 0;
for (int j = 0; j < attrSize; j++)
attributes |= static_cast<unsigned char>(data.at(i * attrSize + j)) << (8 * j);
@ -475,9 +500,9 @@ bool Tileset::loadMetatileAttributes() {
}
bool Tileset::saveMetatileAttributes() {
QFile attrs_file(this->metatile_attrs_path);
if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString()));
QFile file(this->metatile_attrs_path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(file.errorString()));
return false;
}
@ -487,21 +512,36 @@ bool Tileset::saveMetatileAttributes() {
for (int i = 0; i < projectConfig.metatileAttributesSize; i++)
data.append(static_cast<char>(attributes >> (8 * i)));
}
attrs_file.write(data);
file.write(data);
return true;
}
bool Tileset::loadTilesImage(QImage *importedImage) {
QImage image;
bool imported = false;
if (importedImage) {
image = *importedImage;
m_hasUnsavedTilesImage = true;
imported = true;
} else if (QFile::exists(this->tilesImagePath)) {
// No image provided, load from file path.
image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither);
} else {
// Use default image
}
if (image.isNull()) {
logWarn(QString("Failed to load tiles image for %1. Using default tiles image.").arg(this->name));
image = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_Indexed8);
image.fill(0);
}
// Validate image dimensions
if (image.width() % Tile::pixelWidth() || image.height() % Tile::pixelHeight()) {
logError(QString("%1 tiles image has invalid dimensions %2x%3. Dimensions must be a multiple of %4x%5.")
.arg(this->name)
.arg(image.width())
.arg(image.height())
.arg(Tile::pixelWidth())
.arg(Tile::pixelHeight()));
return false;
}
// Validate image contains 16 colors.
@ -515,15 +555,31 @@ bool Tileset::loadTilesImage(QImage *importedImage) {
}
image.setColorTable(colorTable);
}
m_tilesImage = image;
QList<QImage> tiles;
// Cut up the full tiles image into individual tile images.
m_tiles.clear();
for (int y = 0; y < image.height(); y += Tile::pixelHeight())
for (int x = 0; x < image.width(); x += Tile::pixelWidth()) {
QImage tile = image.copy(x, y, Tile::pixelWidth(), Tile::pixelHeight());
tiles.append(tile);
m_tiles.append(image.copy(x, y, Tile::pixelWidth(), Tile::pixelHeight()));
}
this->tilesImage = image;
this->tiles = tiles;
if (m_tiles.length() > maxTiles()) {
logWarn(QString("%1 tile count of %2 exceeds limit of %3. Additional tiles will not be displayed.")
.arg(this->name)
.arg(m_tiles.length())
.arg(maxTiles()));
// Just resize m_tiles so that numTiles() reports the correct tile count.
// We'll leave m_tilesImage alone (it doesn't get displayed, and we don't want to delete the user's image data).
m_tiles = m_tiles.mid(0, maxTiles());
}
if (imported) {
// Only set this flag once we've successfully loaded the tiles image.
m_hasUnsavedTilesImage = true;
}
return true;
}
@ -533,7 +589,7 @@ bool Tileset::saveTilesImage() {
if (!m_hasUnsavedTilesImage)
return true;
if (!this->tilesImage.save(this->tilesImagePath, "PNG")) {
if (!m_tilesImage.save(this->tilesImagePath, "PNG")) {
logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath));
return false;
}

View File

@ -2844,7 +2844,9 @@ void MainWindow::on_actionTileset_Editor_triggered()
openSubWindow(this->tilesetEditor);
MetatileSelection selection = this->editor->metatile_selector_item->getMetatileSelection();
this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId);
if (!selection.metatileItems.isEmpty()) {
this->tilesetEditor->selectMetatile(selection.metatileItems.first().metatileId);
}
}
void MainWindow::initTilesetEditor() {

View File

@ -2461,6 +2461,7 @@ bool Project::readFieldmapMasks() {
projectConfig.blockCollisionMask = blockMask;
if (readBlockMask(elevationMaskName, &blockMask))
projectConfig.blockElevationMask = blockMask;
Block::setLayout();
// Read RSE metatile attribute masks
auto it = defines.find(behaviorMaskName);
@ -3469,7 +3470,6 @@ void Project::applyParsedLimits() {
projectConfig.metatileEncounterTypeMask &= maxMask;
projectConfig.metatileLayerTypeMask &= maxMask;
Block::setLayout();
Metatile::setLayout(this);
Project::num_metatiles_primary = qBound(1, Project::num_metatiles_primary, Block::getMaxMetatileId() + 1);

View File

@ -546,13 +546,13 @@ int MainWindow::getNumSecondaryTilesetMetatiles() {
int MainWindow::getNumPrimaryTilesetTiles() {
if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_primary)
return 0;
return this->editor->layout->tileset_primary->tiles.length();
return this->editor->layout->tileset_primary->numTiles();
}
int MainWindow::getNumSecondaryTilesetTiles() {
if (!this->editor || !this->editor->layout || !this->editor->layout->tileset_secondary)
return 0;
return this->editor->layout->tileset_secondary->tiles.length();
return this->editor->layout->tileset_secondary->numTiles();
}
QString MainWindow::getPrimaryTileset() {
@ -797,12 +797,15 @@ void MainWindow::setMetatileTile(int metatileId, int tileIndex, QJSValue tileObj
QJSValue MainWindow::getTilePixels(int tileId) {
if (tileId < 0 || !this->editor || !this->editor->layout)
return QJSValue();
const int numPixels = Tile::pixelWidth() * Tile::pixelHeight();
QImage tileImage = getTileImage(tileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary);
if (tileImage.isNull() || tileImage.sizeInBytes() < 64)
if (tileImage.isNull() || tileImage.sizeInBytes() < numPixels)
return QJSValue();
const uchar * pixels = tileImage.constBits();
QJSValue pixelArray = Scripting::getEngine()->newArray(64);
for (int i = 0; i < 64; i++) {
QJSValue pixelArray = Scripting::getEngine()->newArray(numPixels);
for (int i = 0; i < numPixels; i++) {
pixelArray.setProperty(i, pixels[i]);
}
return pixelArray;

View File

@ -9,7 +9,7 @@ QImage getCollisionMetatileImage(Block block) {
}
QImage getCollisionMetatileImage(int collision, int elevation) {
const QImage * image = Editor::collisionIcons.at(collision).at(elevation);
const QImage * image = Editor::collisionIcons.value(collision).value(elevation);
return image ? *image : QImage();
}
@ -158,11 +158,7 @@ QImage getMetatileImage(
QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset);
int index = Tile::getIndexInTileset(tileId);
if (!tileset) {
return QImage();
}
return tileset->tiles.value(index, QImage());
return tileset ? tileset->tileImage(tileId) : QImage();
}
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList<QRgb> &palette) {
@ -170,10 +166,10 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se
if (tileImage.isNull()) {
tileImage = QImage(Tile::pixelWidth(), Tile::pixelHeight(), QImage::Format_RGBA8888);
QPainter painter(&tileImage);
painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.at(0));
painter.fillRect(0, 0, tileImage.width(), tileImage.height(), palette.value(0));
} else {
for (int i = 0; i < 16; i++) {
tileImage.setColor(i, palette.at(i));
tileImage.setColor(i, palette.value(i));
}
}
@ -245,26 +241,26 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
const QSize &metatileSize,
bool useTruePalettes)
{
QImage primaryImage = getMetatileSheetImage(primaryTileset,
secondaryTileset,
0,
primaryTileset ? primaryTileset->numMetatiles()-1 : 0,
numMetatilesWide,
layerOrder,
layerOpacity,
metatileSize,
useTruePalettes);
auto createSheetImage = [=](uint16_t start, Tileset *tileset) {
uint16_t end = start;
if (tileset) {
if (tileset->numMetatiles() == 0)
return QImage();
end += tileset->numMetatiles() - 1;
}
return getMetatileSheetImage(primaryTileset,
secondaryTileset,
start,
end,
numMetatilesWide,
layerOrder,
layerOpacity,
metatileSize,
useTruePalettes);
};
uint16_t secondaryMetatileIdStart = Project::getNumMetatilesPrimary();
QImage secondaryImage = getMetatileSheetImage(primaryTileset,
secondaryTileset,
secondaryMetatileIdStart,
secondaryMetatileIdStart + (secondaryTileset ? secondaryTileset->numMetatiles()-1 : 0),
numMetatilesWide,
layerOrder,
layerOpacity,
metatileSize,
useTruePalettes);
QImage primaryImage = createSheetImage(0, primaryTileset);
QImage secondaryImage = createSheetImage(Project::getNumMetatilesPrimary(), secondaryTileset);
QImage image(qMax(primaryImage.width(), secondaryImage.width()), primaryImage.height() + secondaryImage.height(), QImage::Format_RGBA8888);
image.fill(getInvalidImageColor());

View File

@ -783,7 +783,10 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset) {
image = image.convertToFormat(QImage::Format::Format_Indexed8, colorTable);
}
tileset->loadTilesImage(&image);
if (!tileset->loadTilesImage(&image)) {
RecentErrorMessage::show(QStringLiteral("Failed to import tiles."), this);
return;
}
this->refresh();
this->hasUnsavedChanges = true;
}

View File

@ -12,53 +12,40 @@ QPoint TilesetEditorTileSelector::getSelectionDimensions() {
}
}
void TilesetEditorTileSelector::draw() {
if (!this->primaryTileset || !this->secondaryTileset) {
this->setPixmap(QPixmap());
void TilesetEditorTileSelector::updateBasePixmap() {
if (!this->primaryTileset || !this->secondaryTileset || this->numTilesWide == 0) {
this->basePixmap = QPixmap();
return;
}
int totalTiles = Project::getNumTilesTotal();
int primaryLength = this->primaryTileset->tiles.length();
int secondaryLength = this->secondaryTileset->tiles.length();
int height = totalTiles / this->numTilesWide;
QList<QRgb> palette = Tileset::getPalette(this->paletteId, this->primaryTileset, this->secondaryTileset, true);
QImage image(this->numTilesWide * this->cellWidth, height * this->cellHeight, QImage::Format_RGBA8888);
QPainter painter(&image);
for (uint16_t tile = 0; tile < totalTiles; tile++) {
QImage tileImage;
if (tile < primaryLength) {
tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight);
} else if (tile < Project::getNumTilesPrimary()) {
tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888);
tileImage.fill(palette.at(0));
} else if (tile < Project::getNumTilesPrimary() + secondaryLength) {
tileImage = getPalettedTileImage(tile, this->primaryTileset, this->secondaryTileset, this->paletteId, true).scaled(this->cellWidth, this->cellHeight);
} else {
tileImage = QImage(this->cellWidth, this->cellHeight, QImage::Format_RGBA8888);
QPainter painter(&tileImage);
painter.fillRect(0, 0, this->cellWidth, this->cellHeight, palette.at(0));
}
int y = tile / this->numTilesWide;
int x = tile % this->numTilesWide;
QPoint origin = QPoint(x * this->cellWidth, y * this->cellHeight);
painter.drawImage(origin, tileImage);
for (uint16_t tileId = 0; tileId < totalTiles; tileId++) {
QImage tileImage = getPalettedTileImage(tileId, this->primaryTileset, this->secondaryTileset, this->paletteId, true)
.scaled(this->cellWidth, this->cellHeight);
int x = (tileId % this->numTilesWide) * this->cellWidth;
int y = (tileId / this->numTilesWide) * this->cellHeight;
painter.drawImage(x, y, tileImage);
}
if (this->showDivider) {
int row = this->primaryTileset->tiles.length() / this->numTilesWide;
if (this->primaryTileset->tiles.length() % this->numTilesWide != 0) {
// Round up height for incomplete last row
row++;
}
int row = Util::roundUpToMultiple(Project::getNumTilesPrimary(), this->numTilesWide) / this->numTilesWide;
const int y = row * this->cellHeight;
painter.setPen(Qt::white);
painter.drawLine(0, y, this->numTilesWide * this->cellWidth, y);
}
painter.end();
this->setPixmap(QPixmap::fromImage(image));
this->basePixmap = QPixmap::fromImage(image);
}
void TilesetEditorTileSelector::draw() {
if (this->basePixmap.isNull())
updateBasePixmap();
setPixmap(this->basePixmap);
if (!this->externalSelection || (this->externalSelectionWidth == 1 && this->externalSelectionHeight == 1)) {
this->drawSelection();
@ -82,12 +69,14 @@ void TilesetEditorTileSelector::highlight(uint16_t tile) {
void TilesetEditorTileSelector::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
this->updateBasePixmap();
this->draw();
}
void TilesetEditorTileSelector::setPaletteId(int paletteId) {
this->paletteId = paletteId;
this->paletteChanged = true;
this->updateBasePixmap();
this->draw();
}
@ -213,7 +202,7 @@ void TilesetEditorTileSelector::hoverLeaveEvent(QGraphicsSceneHoverEvent*) {
}
QPoint TilesetEditorTileSelector::getTileCoords(uint16_t tile) {
if (tile >= Project::getNumTilesTotal())
if (tile >= Project::getNumTilesTotal() || this->numTilesWide == 0)
{
// Invalid tile.
return QPoint(0, 0);
@ -233,17 +222,20 @@ QImage TilesetEditorTileSelector::buildPrimaryTilesIndexedImage() {
if (!this->primaryTileset)
return QImage();
return buildImage(0, this->primaryTileset->tiles.length());
return buildImage(0, this->primaryTileset->numTiles());
}
QImage TilesetEditorTileSelector::buildSecondaryTilesIndexedImage() {
if (!this->secondaryTileset)
return QImage();
return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->tiles.length());
return buildImage(Project::getNumTilesPrimary(), this->secondaryTileset->numTiles());
}
QImage TilesetEditorTileSelector::buildImage(int tileIdStart, int numTiles) {
if (this->numTilesWide == 0)
return QImage();
int height = qCeil(numTiles / static_cast<double>(this->numTilesWide));
QImage image(this->numTilesWide * Tile::pixelWidth(), height * Tile::pixelHeight(), QImage::Format_RGBA8888);
image.fill(0);