From ff6a51ffa4855a35d01dc43f37085d420c66ae3a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 16 Jun 2023 04:39:32 -0700 Subject: [PATCH] Support 8BPP tileset tile images --- CHANGELOG.md | 3 +++ include/core/tileset.h | 2 ++ include/ui/imageproviders.h | 1 + include/ui/tilemaptileselector.h | 6 ++---- src/core/tileset.cpp | 3 ++- src/mainwindow.cpp | 3 ++- src/project.cpp | 12 ++++++++++-- src/ui/imageproviders.cpp | 9 +++++++++ src/ui/tileseteditor.cpp | 28 ++++++++++------------------ 9 files changed, 41 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ee96b8d..6ae18d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project somewhat adheres to [Semantic Versioning](https://semver.org/sp The **"Breaking Changes"** listed below are changes that have been made in the decompilation projects (e.g. pokeemerald), which porymap requires in order to work properly. It also includes changes to the scripting API that may change the behavior of existing porymap scripts. If porymap is used with a project or API script that is not up-to-date with the breaking changes, then porymap will likely break or behave improperly. ## [Unreleased] +### Added +- Support for 8BPP tileset tile images. + ### Changed - The Palette Editor now remembers the Bit Depth setting. diff --git a/include/core/tileset.h b/include/core/tileset.h index 3f50f41b..b120b2c5 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -38,6 +38,8 @@ public: QList> palettes; QList> palettePreviews; + bool hasUnsavedTilesImage; + static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); diff --git a/include/ui/imageproviders.h b/include/ui/imageproviders.h index db04bdb2..fefa5546 100644 --- a/include/ui/imageproviders.h +++ b/include/ui/imageproviders.h @@ -13,6 +13,7 @@ QImage getMetatileImage(Metatile*, Tileset*, Tileset*, QList, QList, QImage getTileImage(uint16_t, Tileset*, Tileset*); QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false); QImage getGreyscaleTileImage(uint16_t tile, Tileset *primaryTileset, Tileset *secondaryTileset); +void flattenTo4bppImage(QImage * image); static QList greyscalePalette({ qRgb(0, 0, 0), diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 9d419d63..867f6302 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -4,6 +4,7 @@ #include "selectablepixmapitem.h" #include "paletteutil.h" +#include "imageproviders.h" #include using std::shared_ptr; @@ -127,10 +128,7 @@ public: this->tileset = QImage(tilesetFilepath); this->format = format; if (this->tileset.format() == QImage::Format::Format_Indexed8 && this->format == TilemapFormat::BPP_4) { - // Squash pixel data to fit 4BPP. Allows project repo to use 8BPP images for 4BPP tilemaps - uchar * pixel = this->tileset.bits(); - for (int i = 0; i < this->tileset.sizeInBytes(); i++, pixel++) - *pixel %= 16; + flattenTo4bppImage(&this->tileset); } bool err; if (!palFilepath.isEmpty()) { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index 4809ba6d..31d623d5 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -22,7 +22,8 @@ Tileset::Tileset(const Tileset &other) palettePaths(other.palettePaths), metatileLabels(other.metatileLabels), palettes(other.palettes), - palettePreviews(other.palettePreviews) + palettePreviews(other.palettePreviews), + hasUnsavedTilesImage(false) { for (auto tile : other.tiles) { tiles.append(tile.copy()); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4775d15b..36c5b802 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -18,6 +18,7 @@ #include "mapparser.h" #include "prefab.h" #include "montabwidget.h" +#include "imageexport.h" #include #include @@ -1280,7 +1281,7 @@ void MainWindow::on_actionNew_Tileset_triggered() { } newSet.palettes[0][1] = qRgb(255,0,255); newSet.palettePreviews[0][1] = qRgb(255,0,255); - editor->project->saveTilesetTilesImage(&newSet); + exportIndexed4BPPPng(newSet.tilesImage, newSet.tilesImagePath); editor->project->saveTilesetMetatiles(&newSet); editor->project->saveTilesetMetatileAttributes(&newSet); editor->project->saveTilesetPalettes(&newSet); diff --git a/src/project.cpp b/src/project.cpp index b0e3d43c..92a71e2f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -6,7 +6,6 @@ #include "paletteutil.h" #include "tile.h" #include "tileset.h" -#include "imageexport.h" #include "map.h" #include "orderedjson.h" @@ -1008,7 +1007,15 @@ void Project::saveTilesetMetatiles(Tileset *tileset) { } void Project::saveTilesetTilesImage(Tileset *tileset) { - exportIndexed4BPPPng(tileset->tilesImage, tileset->tilesImagePath); + // Only write the tiles image if it was changed. + // Porymap will only ever change an existing tiles image by importing a new one. + if (tileset->hasUnsavedTilesImage) { + if (!tileset->tilesImage.save(tileset->tilesImagePath, "PNG")) { + logError(QString("Failed to save tiles image '%1'").arg(tileset->tilesImagePath)); + return; + } + tileset->hasUnsavedTilesImage = false; + } } void Project::saveTilesetPalettes(Tileset *tileset) { @@ -1350,6 +1357,7 @@ void Project::loadTilesetAssets(Tileset* tileset) { QImage image; if (QFile::exists(tileset->tilesImagePath)) { image = QImage(tileset->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); + flattenTo4bppImage(&image); } else { image = QImage(8, 8, QImage::Format_Indexed8); } diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 838f4db7..891cfbba 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -169,3 +169,12 @@ QImage getPalettedTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *s QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) { return getColoredTileImage(tileId, primaryTileset, secondaryTileset, greyscalePalette); } + +// gbagfx allows 4bpp image data to be represented with 8bpp .png files by considering only the lower 4 bits of each pixel. +// Reproduce that here to support this type of image use. +void flattenTo4bppImage(QImage * image) { + if (!image) return; + uchar * pixel = image->bits(); + for (int i = 0; i < image->sizeInBytes(); i++, pixel++) + *pixel %= 16; +} diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index c16aeaf4..d8a9b740 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -701,15 +701,6 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { msgBox.setIcon(QMessageBox::Icon::Critical); msgBox.exec(); return; - } else if (palette.length() != 16) { - QMessageBox msgBox(this); - msgBox.setText("Failed to import palette."); - QString message = QString("The palette must have exactly 16 colors, but it has %1.").arg(palette.length()); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; } QVector colorTable = palette.toVector(); @@ -717,20 +708,21 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { } // 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, or it must be un-indexed. The provided image has %1 indexed colors.") - .arg(image.colorCount())); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; + int colorCount = image.colorCount(); + if (colorCount > 16) { + flattenTo4bppImage(&image); + } else if (colorCount < 16) { + QVector colorTable = image.colorTable(); + for (int i = colorTable.length(); i < 16; i++) { + colorTable.append(Qt::black); + } + image.setColorTable(colorTable); } this->project->loadTilesetTiles(tileset, image); this->refresh(); this->hasUnsavedChanges = true; + tileset->hasUnsavedTilesImage = true; } void TilesetEditor::closeEvent(QCloseEvent *event)