diff --git a/forms/tileseteditor.ui b/forms/tileseteditor.ui index db1dec68..735606d2 100644 --- a/forms/tileseteditor.ui +++ b/forms/tileseteditor.ui @@ -383,6 +383,9 @@ File + + + @@ -395,6 +398,16 @@ Ctrl+S + + + Import Primary Tiles + + + + + Import Secondary Tiles + + diff --git a/include/core/tileset.h b/include/core/tileset.h index c3d55b1d..aea9f458 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -21,6 +21,8 @@ public: QString callback_label; QString metatile_attrs_label; QString metatile_attrs_path; + QString tilesImagePath; + QImage tilesImage; QList *tiles = nullptr; QList *metatiles = nullptr; diff --git a/include/project.h b/include/project.h index 4aa8d35e..21acd40a 100644 --- a/include/project.h +++ b/include/project.h @@ -74,6 +74,8 @@ public: void readMapsWithConnections(); void loadMapTilesets(Map*); void loadTilesetAssets(Tileset*); + void loadTilesetTiles(Tileset*, QImage); + void loadTilesetMetatiles(Tileset*); void saveBlockdata(Map*); void saveMapBorder(Map*); @@ -133,6 +135,7 @@ private: void saveMapConnections(Map*); void saveTilesetMetatileAttributes(Tileset*); void saveTilesetMetatiles(Tileset*); + void saveTilesetTilesImage(Tileset*); void updateMapsWithConnections(Map*); void saveMapsWithConnections(); void saveMapLayoutsTable(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index c179bfd0..9b6d6e67 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -41,12 +41,18 @@ private slots: void on_actionSave_Tileset_triggered(); + void on_actionImport_Primary_Tiles_triggered(); + + void on_actionImport_Secondary_Tiles_triggered(); + private: void initMetatileSelector(); void initTileSelector(); void initSelectedTileItem(); void initMetatileLayersItem(); void drawSelectedTile(); + void importTilesetTiles(Tileset*, bool); + void refresh(); Ui::TilesetEditor *ui; TilesetEditorMetatileSelector *metatileSelector = nullptr; TilesetEditorTileSelector *tileSelector = nullptr; diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 5bb51e23..f62135ad 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -24,6 +24,8 @@ Tileset* Tileset::copy() { copy->callback_label = this->callback_label; copy->metatile_attrs_label = this->metatile_attrs_label; copy->metatile_attrs_path = this->metatile_attrs_path; + copy->tilesImage = this->tilesImage.copy(); + copy->tilesImagePath = this->tilesImagePath; copy->tiles = new QList; for (QImage tile : *this->tiles) { copy->tiles->append(tile.copy()); diff --git a/src/editor.cpp b/src/editor.cpp index 878e4a70..08f0c766 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -965,7 +965,6 @@ void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) { if (map->layout->tileset_primary_label != tilesetLabel || forceLoad) { - qDebug() << "updatePrimaryTileset"; map->layout->tileset_primary_label = tilesetLabel; map->layout->tileset_primary = project->getTileset(tilesetLabel, forceLoad); emit tilesetChanged(map->name); @@ -974,7 +973,7 @@ void Editor::updatePrimaryTileset(QString tilesetLabel, bool forceLoad) void Editor::updateSecondaryTileset(QString tilesetLabel, bool forceLoad) { - if (map->layout->tileset_secondary_label != tilesetLabel) + if (map->layout->tileset_secondary_label != tilesetLabel || forceLoad) { map->layout->tileset_secondary_label = tilesetLabel; map->layout->tileset_secondary = project->getTileset(tilesetLabel, forceLoad); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 83e0dccb..7f64fdca 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1244,7 +1244,7 @@ void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected) void MainWindow::on_actionTileset_Editor_triggered() { if (!this->tilesetEditor) { - this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label); + this->tilesetEditor = new TilesetEditor(this->editor->project, this->editor->map->layout->tileset_primary_label, this->editor->map->layout->tileset_secondary_label, this); connect(this->tilesetEditor, SIGNAL(tilesetsSaved(QString, QString)), this, SLOT(onTilesetsSaved(QString, QString))); } diff --git a/src/project.cpp b/src/project.cpp index 2bc67c03..714868fa 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -581,6 +581,8 @@ void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { saveTilesetMetatileAttributes(secondaryTileset); saveTilesetMetatiles(primaryTileset); saveTilesetMetatiles(secondaryTileset); + saveTilesetTilesImage(primaryTileset); + saveTilesetTilesImage(secondaryTileset); } void Project::saveTilesetMetatileAttributes(Tileset *tileset) { @@ -619,6 +621,11 @@ void Project::saveTilesetMetatiles(Tileset *tileset) { } } +void Project::saveTilesetTilesImage(Tileset *tileset) { + qDebug() << QString("saving tiles png to '%1'").arg(tileset->tilesImagePath); + tileset->tilesImage.save(tileset->tilesImagePath); +} + void Project::loadMapTilesets(Map* map) { if (map->layout->has_unsaved_changes) { return; @@ -841,22 +848,59 @@ void Project::loadTilesetAssets(Tileset* tileset) { tileset->metatile_attrs_path = dir_path + "/metatile_attributes.bin"; } - // tiles tiles_path = fixGraphicPath(tiles_path); - QImage *image = new QImage(tiles_path); - //image->setColor(0, qRgb(0xff, 0, 0)); // debug + tileset->tilesImagePath = tiles_path; + QImage image = QImage(tileset->tilesImagePath); + this->loadTilesetTiles(tileset, image); + this->loadTilesetMetatiles(tileset); + // palettes + QList> *palettes = new QList>; + for (int i = 0; i < palette_paths->length(); i++) { + QString path = palette_paths->value(i); + // the palettes are not compressed. this should never happen. it's only a precaution. + path = path.replace(QRegExp("\\.lz$"), ""); + // TODO default to .pal (JASC-PAL) + // just use .gbapal for now + QFile file(path); + QList palette; + if (file.open(QIODevice::ReadOnly)) { + QByteArray data = file.readAll(); + for (int j = 0; j < 16; j++) { + uint16_t word = data[j*2] & 0xff; + word += (data[j*2 + 1] & 0xff) << 8; + int red = word & 0x1f; + int green = (word >> 5) & 0x1f; + int blue = (word >> 10) & 0x1f; + QRgb color = qRgb(red * 8, green * 8, blue * 8); + palette.append(color); + } + } else { + for (int j = 0; j < 16; j++) { + palette.append(qRgb(j * 16, j * 16, j * 16)); + } + qDebug() << QString("Could not open palette path '%1'").arg(path); + } + + palettes->append(palette); + } + tileset->palettes = palettes; +} + +void Project::loadTilesetTiles(Tileset *tileset, QImage image) { QList *tiles = new QList; int w = 8; int h = 8; - for (int y = 0; y < image->height(); y += h) - for (int x = 0; x < image->width(); x += w) { - QImage tile = image->copy(x, y, w, h); + for (int y = 0; y < image.height(); y += h) + for (int x = 0; x < image.width(); x += w) { + QImage tile = image.copy(x, y, w, h); tiles->append(tile); } + tileset->tilesImage = image; tileset->tiles = tiles; +} - // metatiles +void Project::loadTilesetMetatiles(Tileset* tileset) { QFile metatiles_file(tileset->metatiles_path); if (metatiles_file.open(QIODevice::ReadOnly)) { QByteArray data = metatiles_file.readAll(); @@ -902,38 +946,6 @@ void Project::loadTilesetAssets(Tileset* tileset) { } else { qDebug() << QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path); } - - // palettes - QList> *palettes = new QList>; - for (int i = 0; i < palette_paths->length(); i++) { - QString path = palette_paths->value(i); - // the palettes are not compressed. this should never happen. it's only a precaution. - path = path.replace(QRegExp("\\.lz$"), ""); - // TODO default to .pal (JASC-PAL) - // just use .gbapal for now - QFile file(path); - QList palette; - if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); - for (int j = 0; j < 16; j++) { - uint16_t word = data[j*2] & 0xff; - word += (data[j*2 + 1] & 0xff) << 8; - int red = word & 0x1f; - int green = (word >> 5) & 0x1f; - int blue = (word >> 10) & 0x1f; - QRgb color = qRgb(red * 8, green * 8, blue * 8); - palette.append(color); - } - } else { - for (int j = 0; j < 16; j++) { - palette.append(qRgb(j * 16, j * 16, j * 16)); - } - qDebug() << QString("Could not open palette path '%1'").arg(path); - } - - palettes->append(palette); - } - tileset->palettes = palettes; } Blockdata* Project::readBlockdata(QString path) { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 033d94c9..606fdc93 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -1,6 +1,9 @@ #include "tileseteditor.h" #include "ui_tileseteditor.h" #include "imageproviders.h" +#include +#include +#include TilesetEditor::TilesetEditor(Project *project, QString primaryTilesetLabel, QString secondaryTilesetLabel, QWidget *parent) : QMainWindow(parent), @@ -47,7 +50,10 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi Tileset *secondaryTileset = project->getTileset(secondaryTilesetLabel); this->primaryTileset = primaryTileset->copy(); this->secondaryTileset = secondaryTileset->copy(); + this->refresh(); +} +void TilesetEditor::refresh() { this->metatileSelector->setTilesets(this->primaryTileset, this->secondaryTileset); this->tileSelector->setTilesets(this->primaryTileset, this->secondaryTileset); this->metatileLayersItem->setTilesets(this->primaryTileset, this->secondaryTileset); @@ -209,3 +215,77 @@ void TilesetEditor::on_actionSave_Tileset_triggered() emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name); this->ui->statusbar->showMessage(QString("Saved primary and secondary Tilesets!"), 5000); } + +void TilesetEditor::on_actionImport_Primary_Tiles_triggered() +{ + this->importTilesetTiles(this->primaryTileset, true); +} + +void TilesetEditor::on_actionImport_Secondary_Tiles_triggered() +{ + this->importTilesetTiles(this->secondaryTileset, false); +} + +void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { + QString descriptor = primary ? "primary" : "secondary"; + QString descriptorCaps = primary ? "Primary" : "Secondary"; + + QString filepath = QFileDialog::getOpenFileName( + this, + QString("Import %1 Tileset Tiles Image").arg(descriptorCaps), + this->project->root, + "Image Files (*.png)"); + if (filepath.isEmpty()) { + return; + } + + qDebug() << QString("Importing %1 tileset tiles '%2'").arg(descriptor).arg(filepath); + + // Validate image dimensions. + QImage image = QImage(filepath); + if (image.width() == 0 || image.height() == 0 || image.width() % 8 != 0 || image.height() % 8 != 0) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import tiles."); + msgBox.setInformativeText(QString("The image dimensions (%1 x %2) are invalid. Width and height must be multiples of 8 pixels.") + .arg(image.width()) + .arg(image.height())); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + return; + } + + // Validate image is properly indexed to 16 colors. + if (image.colorCount() != 16) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import tiles."); + msgBox.setInformativeText(QString("The image must be indexed and contain 16 total colors. The provided image has %1 indexed colors.") + .arg(image.colorCount())); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + return; + } + + // Validate total number of tiles in image. + int numTilesWide = image.width() / 16; + int numTilesHigh = image.height() / 16; + int totalTiles = numTilesHigh * numTilesWide; + int maxAllowedTiles = primary ? Project::getNumTilesPrimary() : Project::getNumTilesTotal() - Project::getNumTilesPrimary(); + if (totalTiles > maxAllowedTiles) { + QMessageBox msgBox(this); + msgBox.setText("Failed to import tiles."); + msgBox.setInformativeText(QString("The maximum number of tiles allowed in the %1 tileset is %2, but the provided image contains %3 total tiles.") + .arg(descriptor) + .arg(maxAllowedTiles) + .arg(totalTiles)); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.exec(); + return; + } + + this->project->loadTilesetTiles(tileset, image); + this->project->loadTilesetMetatiles(tileset); + this->refresh(); +}