Merge pull request #536 from GriffinRichards/4bpp

Support 8BPP tileset tile images
This commit is contained in:
GriffinR 2023-08-13 22:33:07 -04:00 committed by GitHub
commit d28849a533
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 41 additions and 26 deletions

View file

@ -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.
- The min/max levels on the Wild Pokémon tab will now adjust automatically if they invalidate each other.

View file

@ -38,6 +38,8 @@ public:
QList<QList<QRgb>> palettes;
QList<QList<QRgb>> palettePreviews;
bool hasUnsavedTilesImage;
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
static Tileset* getTileTileset(int, Tileset*, Tileset*);
static Metatile* getMetatile(int, Tileset*, Tileset*);

View file

@ -13,6 +13,7 @@ QImage getMetatileImage(Metatile*, Tileset*, Tileset*, QList<int>, QList<float>,
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<QRgb> greyscalePalette({
qRgb(0, 0, 0),

View file

@ -4,6 +4,7 @@
#include "selectablepixmapitem.h"
#include "paletteutil.h"
#include "imageproviders.h"
#include <memory>
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()) {

View file

@ -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());

View file

@ -18,6 +18,7 @@
#include "mapparser.h"
#include "prefab.h"
#include "montabwidget.h"
#include "imageexport.h"
#include <QFileDialog>
#include <QClipboard>
@ -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);

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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<QRgb> 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<QRgb> 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)