mirror of
https://github.com/huderlem/porymap.git
synced 2026-03-21 17:45:44 -05:00
Add setting to show unused palette colors
This commit is contained in:
parent
d3d30ae0f2
commit
2f2f71948a
|
|
@ -38,6 +38,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_AllColorsUsed">
|
||||
<property name="text">
|
||||
<string>(All colors used)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
@ -133,8 +140,15 @@
|
|||
<addaction name="actionUndo"/>
|
||||
<addaction name="actionRedo"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuView">
|
||||
<property name="title">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<addaction name="actionShow_Unused_Colors"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEdit"/>
|
||||
<addaction name="menuView"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<action name="actionUndo">
|
||||
|
|
@ -164,6 +178,14 @@
|
|||
<string>Import Palette</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionShow_Unused_Colors">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Unused Colors</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ public:
|
|||
bool showTilesetEditorLayerGrid;
|
||||
bool showTilesetEditorDivider;
|
||||
bool showTilesetEditorRawAttributes;
|
||||
bool showPaletteEditorUnusedColors;
|
||||
bool monitorFiles;
|
||||
bool tilesetCheckerboardFill;
|
||||
bool newMapHeaderSectionExpanded;
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@ public:
|
|||
int getZ() const { return this->elevation; }
|
||||
int getElevation() const { return this->elevation; }
|
||||
|
||||
int getPixelX() const { return (this->x * 16) - qMax(0, (pixmap.width() - 16) / 2); }
|
||||
int getPixelY() const { return (this->y * 16) - qMax(0, pixmap.height() - 16); }
|
||||
int getPixelX() const;
|
||||
int getPixelY() const;
|
||||
|
||||
virtual EventFrame *getEventFrame();
|
||||
virtual EventFrame *createEventFrame() = 0;
|
||||
|
|
|
|||
|
|
@ -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 numPixels() { return Tile::pixelWidth() * Tile::pixelHeight(); }
|
||||
static constexpr int sizeInBytes() { return sizeof(uint16_t); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ public:
|
|||
static QString stripPrefix(const QString &fullName);
|
||||
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
|
||||
static Tileset* getTileTileset(int, Tileset*, Tileset*);
|
||||
static const Tileset* getTileTileset(int, const Tileset*, const Tileset*);
|
||||
static Metatile* getMetatile(int, Tileset*, Tileset*);
|
||||
static Tileset* getMetatileLabelTileset(int, Tileset*, Tileset*);
|
||||
static QString getMetatileLabel(int, Tileset *, Tileset *);
|
||||
|
|
@ -92,6 +93,11 @@ public:
|
|||
|
||||
QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); }
|
||||
|
||||
QSet<int> getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet<int> &searchColors = {}) const;
|
||||
|
||||
static constexpr int maxPalettes() { return 16; }
|
||||
static constexpr int numColorsPerPalette() { return 16; }
|
||||
|
||||
private:
|
||||
QList<Metatile*> m_metatiles;
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ public:
|
|||
QStringList primaryTilesetLabels;
|
||||
QStringList secondaryTilesetLabels;
|
||||
QStringList tilesetLabelsOrdered;
|
||||
QSet<QString> getPairedTilesetLabels(Tileset *tileset) const;
|
||||
|
||||
bool readMapGroups();
|
||||
void addNewMapGroup(const QString &groupName);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
|
|||
bool useTruePalettes = false);
|
||||
|
||||
|
||||
QImage getTileImage(uint16_t, Tileset*, Tileset*);
|
||||
QImage getTileImage(uint16_t, const Tileset*, const Tileset*);
|
||||
QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false);
|
||||
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList<QRgb> &palette);
|
||||
QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset);
|
||||
|
|
|
|||
|
|
@ -24,9 +24,14 @@ class PaletteEditor : public QMainWindow {
|
|||
public:
|
||||
explicit PaletteEditor(Project*, Tileset*, Tileset*, int paletteId, QWidget *parent = nullptr);
|
||||
~PaletteEditor();
|
||||
|
||||
void setPaletteId(int);
|
||||
int currentPaletteId() const;
|
||||
|
||||
void setTilesets(Tileset*, Tileset*);
|
||||
|
||||
bool showingUnusedColors() const;
|
||||
|
||||
private:
|
||||
Ui::PaletteEditor *ui;
|
||||
Project *project = nullptr;
|
||||
|
|
@ -36,13 +41,18 @@ private:
|
|||
Tileset *secondaryTileset;
|
||||
|
||||
QList<History<PaletteHistoryItem*>> palettesHistory;
|
||||
QMap<int,QSet<int>> unusedColorCache;
|
||||
|
||||
Tileset* getTileset(int paletteId);
|
||||
Tileset* getTileset(int paletteId) const;
|
||||
void refreshColorInputs();
|
||||
void commitEditHistory();
|
||||
void commitEditHistory(int paletteId);
|
||||
void restoreWindowState();
|
||||
void onWindowActivated();
|
||||
void invalidateCache();
|
||||
void closeEvent(QCloseEvent*);
|
||||
void setColorInputTitles(bool show);
|
||||
QSet<int> getUnusedColorIds() const;
|
||||
|
||||
void setRgb(int index, QRgb rgb);
|
||||
void setPalette(int paletteId, const QList<QRgb> &palette);
|
||||
|
|
@ -50,7 +60,7 @@ private:
|
|||
void setBitDepth(int bits);
|
||||
int bitDepth = 24;
|
||||
|
||||
static const int numColors = 16;
|
||||
static const int numColors = Tileset::numColorsPerPalette();
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@ void PorymapConfig::reset() {
|
|||
this->showTilesetEditorLayerGrid = true;
|
||||
this->showTilesetEditorDivider = false;
|
||||
this->showTilesetEditorRawAttributes = false;
|
||||
this->showPaletteEditorUnusedColors = false;
|
||||
this->monitorFiles = true;
|
||||
this->tilesetCheckerboardFill = true;
|
||||
this->newMapHeaderSectionExpanded = false;
|
||||
|
|
@ -469,6 +470,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->showTilesetEditorDivider = getConfigBool(key, value);
|
||||
} else if (key == "show_tileset_editor_raw_attributes") {
|
||||
this->showTilesetEditorRawAttributes = getConfigBool(key, value);
|
||||
} else if (key == "show_palette_editor_unused_colors") {
|
||||
this->showPaletteEditorUnusedColors = getConfigBool(key, value);
|
||||
} else if (key == "monitor_files") {
|
||||
this->monitorFiles = getConfigBool(key, value);
|
||||
} else if (key == "tileset_checkerboard_fill") {
|
||||
|
|
@ -609,6 +612,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
|
|||
map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0");
|
||||
map.insert("show_tileset_editor_divider", this->showTilesetEditorDivider ? "1" : "0");
|
||||
map.insert("show_tileset_editor_raw_attributes", this->showTilesetEditorRawAttributes ? "1" : "0");
|
||||
map.insert("show_palette_editor_unused_colors", this->showPaletteEditorUnusedColors ? "1" : "0");
|
||||
map.insert("monitor_files", this->monitorFiles ? "1" : "0");
|
||||
map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0");
|
||||
map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "eventframes.h"
|
||||
#include "project.h"
|
||||
#include "config.h"
|
||||
#include "metatile.h"
|
||||
|
||||
Event* Event::create(Event::Type type) {
|
||||
switch (type) {
|
||||
|
|
@ -23,6 +24,14 @@ Event::~Event() {
|
|||
delete this->eventFrame;
|
||||
}
|
||||
|
||||
int Event::getPixelX() const {
|
||||
return (this->x * Metatile::pixelWidth()) - qMax(0, (this->pixmap.width() - Metatile::pixelWidth()) / 2);
|
||||
}
|
||||
|
||||
int Event::getPixelY() const {
|
||||
return (this->y * Metatile::pixelHeight()) - qMax(0, this->pixmap.height() - Metatile::pixelHeight());
|
||||
}
|
||||
|
||||
EventFrame *Event::getEventFrame() {
|
||||
if (!this->eventFrame) createEventFrame();
|
||||
return this->eventFrame;
|
||||
|
|
|
|||
|
|
@ -111,6 +111,11 @@ int Tileset::maxTiles() const {
|
|||
}
|
||||
|
||||
Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
|
||||
return const_cast<Tileset*>(getTileTileset(tileId, static_cast<const Tileset*>(primaryTileset), static_cast<const Tileset*>(secondaryTileset)));
|
||||
}
|
||||
|
||||
// Get the tileset *expected* to contain the given 'tileId'. Note that this does not mean the tile actually exists in that tileset.
|
||||
const Tileset* Tileset::getTileTileset(int tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
|
||||
if (tileId < Project::getNumTilesPrimary()) {
|
||||
return primaryTileset;
|
||||
} else if (tileId < Project::getNumTilesTotal()) {
|
||||
|
|
@ -120,6 +125,7 @@ Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *s
|
|||
}
|
||||
}
|
||||
|
||||
// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset.
|
||||
Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
|
||||
if (metatileId < Project::getNumMetatilesPrimary()) {
|
||||
return primaryTileset;
|
||||
|
|
@ -334,7 +340,7 @@ bool Tileset::appendToGraphics(const QString &filepath, const QString &friendlyN
|
|||
dataString.append(QString("\t.incbin \"%1\"\n").arg(tilesPath));
|
||||
} else {
|
||||
// Append to C file
|
||||
dataString.append(QString("const u16 gTilesetPalettes_%1[][16] =\n{\n").arg(friendlyName));
|
||||
dataString.append(QString("const u16 gTilesetPalettes_%1[][%2] =\n{\n").arg(friendlyName).arg(Tileset::numColorsPerPalette()));
|
||||
for (int i = 0; i < Project::getNumPalettesTotal(); i++)
|
||||
dataString.append(QString(" INCBIN_U16(\"%1%2%3\"),\n").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0')).arg(palettesExt));
|
||||
dataString.append("};\n");
|
||||
|
|
@ -546,13 +552,13 @@ bool Tileset::loadTilesImage(QImage *importedImage) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Validate image contains 16 colors.
|
||||
// Validate the number of colors in the image.
|
||||
int colorCount = image.colorCount();
|
||||
if (colorCount > 16) {
|
||||
if (colorCount > Tileset::numColorsPerPalette()) {
|
||||
flattenTo4bppImage(&image);
|
||||
} else if (colorCount < 16) {
|
||||
} else if (colorCount < Tileset::numColorsPerPalette()) {
|
||||
QVector<QRgb> colorTable = image.colorTable();
|
||||
for (int i = colorTable.length(); i < 16; i++) {
|
||||
for (int i = colorTable.length(); i < Tileset::numColorsPerPalette(); i++) {
|
||||
colorTable.append(0);
|
||||
}
|
||||
image.setColorTable(colorTable);
|
||||
|
|
@ -616,8 +622,9 @@ bool Tileset::loadPalettes() {
|
|||
// Either the palette failed to load, or no palette exists.
|
||||
// We expect tilesets to have a certain number of palettes,
|
||||
// so fill this palette with dummy colors.
|
||||
for (int j = 0; j < 16; j++) {
|
||||
palette.append(qRgb(j * 16, j * 16, j * 16));
|
||||
for (int j = 0; j < Tileset::numColorsPerPalette(); j++) {
|
||||
int colorComponent = j * (256 / Tileset::numColorsPerPalette());
|
||||
palette.append(qRgb(colorComponent, colorComponent, colorComponent));
|
||||
}
|
||||
}
|
||||
this->palettes.append(palette);
|
||||
|
|
@ -630,7 +637,7 @@ bool Tileset::savePalettes() {
|
|||
bool success = true;
|
||||
int numPalettes = qMin(this->palettePaths.length(), this->palettes.length());
|
||||
for (int i = 0; i < numPalettes; i++) {
|
||||
if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16))
|
||||
if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, Tileset::numColorsPerPalette()))
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
|
|
@ -658,3 +665,45 @@ bool Tileset::save() {
|
|||
QString Tileset::stripPrefix(const QString &fullName) {
|
||||
return QString(fullName).replace(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix), "");
|
||||
}
|
||||
|
||||
// Find which of the specified color IDs in 'searchColors' are not used by any of this tileset's metatiles.
|
||||
// The 'pairedTileset' may be used to get the tile images for any tiles that don't belong to this tileset.
|
||||
// If 'searchColors' is empty, it will for search for all unused colors.
|
||||
QSet<int> Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet<int> &searchColors) const {
|
||||
QSet<int> unusedColors = searchColors;
|
||||
if (unusedColors.isEmpty()) {
|
||||
// Search for all colors
|
||||
for (int i = 0; i < Tileset::numColorsPerPalette(); i++) {
|
||||
unusedColors.insert(i);
|
||||
}
|
||||
}
|
||||
const Tileset *primaryTileset = this->is_secondary ? pairedTileset : this;
|
||||
const Tileset *secondaryTileset = this->is_secondary ? this : pairedTileset;
|
||||
QSet<uint16_t> seenTileIds;
|
||||
for (const auto &metatile : m_metatiles)
|
||||
for (const auto &tile : metatile->tiles) {
|
||||
if (tile.palette != paletteId)
|
||||
continue;
|
||||
|
||||
// Save time by ignoring tiles we've already inspected.
|
||||
if (seenTileIds.contains(tile.tileId))
|
||||
continue;
|
||||
seenTileIds.insert(tile.tileId);
|
||||
|
||||
QImage image = getTileImage(tile.tileId, primaryTileset, secondaryTileset);
|
||||
if (image.isNull() || image.sizeInBytes() < Tile::numPixels())
|
||||
continue;
|
||||
|
||||
const uchar * pixels = image.constBits();
|
||||
for (int i = 0; i < Tile::numPixels(); i++) {
|
||||
auto it = unusedColors.constFind(pixels[i]);
|
||||
if (it != unusedColors.constEnd()) {
|
||||
unusedColors.erase(it);
|
||||
if (unusedColors.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return unusedColors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1517,7 +1517,7 @@ void Project::readTilesetPaths(Tileset* tileset) {
|
|||
tileset->metatile_attrs_path = defaultPath + "/metatile_attributes.bin";
|
||||
if (tileset->palettePaths.isEmpty()) {
|
||||
QString palettes_dir_path = defaultPath + "/palettes/";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < Tileset::maxPalettes(); i++) {
|
||||
tileset->palettePaths.append(palettes_dir_path + QString("%1").arg(i, 2, 10, QLatin1Char('0')) + ".pal");
|
||||
}
|
||||
}
|
||||
|
|
@ -1579,9 +1579,9 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
|
|||
}
|
||||
|
||||
// Create default palettes
|
||||
for(int i = 0; i < 16; ++i) {
|
||||
for(int i = 0; i < Tileset::maxPalettes(); ++i) {
|
||||
QList<QRgb> currentPal;
|
||||
for(int i = 0; i < 16;++i) {
|
||||
for(int i = 0; i < Tileset::numColorsPerPalette();++i) {
|
||||
currentPal.append(qRgb(0,0,0));
|
||||
}
|
||||
tileset->palettes.append(currentPal);
|
||||
|
|
@ -2347,7 +2347,7 @@ bool Project::readFieldmapProperties() {
|
|||
logWarn(QString("Value for '%1' not found. Using default (%2) instead.").arg(name).arg(*dest));
|
||||
}
|
||||
};
|
||||
loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter.
|
||||
loadDefine(numPalsTotalName, &Project::num_pals_total, 2, Tileset::maxPalettes());
|
||||
loadDefine(numTilesTotalName, &Project::num_tiles_total, 2, 1024); // 1024 is fixed because we store tile IDs in a 10-bit field.
|
||||
loadDefine(numPalsPrimaryName, &Project::num_pals_primary, 1, Project::num_pals_total - 1);
|
||||
loadDefine(numTilesPrimaryName, &Project::num_tiles_primary, 1, Project::num_tiles_total - 1);
|
||||
|
|
@ -3518,3 +3518,18 @@ bool Project::hasUnsavedChanges() {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Searches the project's map layouts to find the names of the tilesets that the provided tileset gets paired with.
|
||||
QSet<QString> Project::getPairedTilesetLabels(Tileset *tileset) const {
|
||||
QSet<QString> pairedLabels;
|
||||
for (const auto &layout : this->mapLayouts) {
|
||||
if (tileset->is_secondary) {
|
||||
if (layout->tileset_secondary_label == tileset->name) {
|
||||
pairedLabels.insert(layout->tileset_primary_label);
|
||||
}
|
||||
} else if (layout->tileset_primary_label == tileset->name) {
|
||||
pairedLabels.insert(layout->tileset_secondary_label);
|
||||
}
|
||||
}
|
||||
return pairedLabels;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -346,10 +346,7 @@ void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList<QLi
|
|||
return;
|
||||
if (paletteIndex >= tileset->palettes.size())
|
||||
return;
|
||||
if (colors.size() != 16)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < qMin(colors.length(), Tileset::numColorsPerPalette()); i++) {
|
||||
if (colors[i].size() != 3)
|
||||
continue;
|
||||
tileset->palettes[paletteIndex][i] = qRgb(colors[i][0], colors[i][1], colors[i][2]);
|
||||
|
|
@ -457,10 +454,7 @@ void MainWindow::setTilesetPalettePreview(Tileset *tileset, int paletteIndex, QL
|
|||
return;
|
||||
if (paletteIndex >= tileset->palettePreviews.size())
|
||||
return;
|
||||
if (colors.size() != 16)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < qMin(colors.length(), Tileset::numColorsPerPalette()); i++) {
|
||||
if (colors[i].size() != 3)
|
||||
continue;
|
||||
tileset->palettePreviews[paletteIndex][i] = qRgb(colors[i][0], colors[i][1], colors[i][2]);
|
||||
|
|
@ -798,14 +792,13 @@ 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() < numPixels)
|
||||
if (tileImage.isNull() || tileImage.sizeInBytes() < Tile::numPixels())
|
||||
return QJSValue();
|
||||
|
||||
const uchar * pixels = tileImage.constBits();
|
||||
QJSValue pixelArray = Scripting::getEngine()->newArray(numPixels);
|
||||
for (int i = 0; i < numPixels; i++) {
|
||||
QJSValue pixelArray = Scripting::getEngine()->newArray(Tile::numPixels());
|
||||
for (int i = 0; i < Tile::numPixels(); i++) {
|
||||
pixelArray.setProperty(i, pixels[i]);
|
||||
}
|
||||
return pixelArray;
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ QImage getMetatileImage(
|
|||
return metatileImage;
|
||||
}
|
||||
|
||||
QImage getTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
|
||||
Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset);
|
||||
QImage getTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
|
||||
const Tileset *tileset = Tileset::getTileTileset(tileId, primaryTileset, secondaryTileset);
|
||||
return tileset ? tileset->tileImage(tileId) : QImage();
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se
|
|||
tileImage = QImage(Tile::pixelSize(), QImage::Format_RGBA8888);
|
||||
tileImage.fill(getInvalidImageColor());
|
||||
} else {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < Tileset::numColorsPerPalette(); i++) {
|
||||
tileImage.setColor(i, palette.value(i, getInvalidImageColor().rgb()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "log.h"
|
||||
#include "filedialog.h"
|
||||
#include "message.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
|
||||
PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) :
|
||||
|
|
@ -21,7 +22,7 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
|
|||
this->colorInputs.clear();
|
||||
const int numColorsPerRow = 4;
|
||||
for (int i = 0; i < this->numColors; i++) {
|
||||
auto colorInput = new ColorInputWidget(QString("Color %1").arg(i));
|
||||
auto colorInput = new ColorInputWidget;
|
||||
connect(colorInput, &ColorInputWidget::colorChanged, [this, i](QRgb color) { setRgb(i, color); });
|
||||
connect(colorInput, &ColorInputWidget::editingFinished, [this] { commitEditHistory(); });
|
||||
this->colorInputs.append(colorInput);
|
||||
|
|
@ -45,17 +46,37 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
|
|||
connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(15); });
|
||||
connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(24); });
|
||||
|
||||
this->ui->actionShow_Unused_Colors->setChecked(porymapConfig.showPaletteEditorUnusedColors);
|
||||
connect(this->ui->actionShow_Unused_Colors, &QAction::toggled, this, &PaletteEditor::setColorInputTitles);
|
||||
|
||||
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
|
||||
connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::onWindowActivated);
|
||||
this->installEventFilter(filter);
|
||||
|
||||
this->setPaletteId(paletteId);
|
||||
this->commitEditHistory();
|
||||
this->restoreWindowState();
|
||||
}
|
||||
|
||||
PaletteEditor::~PaletteEditor()
|
||||
{
|
||||
PaletteEditor::~PaletteEditor() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
Tileset* PaletteEditor::getTileset(int paletteId) {
|
||||
void PaletteEditor::onWindowActivated() {
|
||||
// Rather than try to keep track of metatile/tile changes that affect which colors are used,
|
||||
// we'll just refresh when the window is activated.
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
int PaletteEditor::currentPaletteId() const {
|
||||
return ui->spinBox_PaletteId->value();
|
||||
}
|
||||
|
||||
bool PaletteEditor::showingUnusedColors() const {
|
||||
return ui->actionShow_Unused_Colors->isChecked();
|
||||
}
|
||||
|
||||
Tileset* PaletteEditor::getTileset(int paletteId) const {
|
||||
return (paletteId < Project::getNumPalettesPrimary())
|
||||
? this->primaryTileset
|
||||
: this->secondaryTileset;
|
||||
|
|
@ -70,8 +91,7 @@ void PaletteEditor::setBitDepth(int bits) {
|
|||
}
|
||||
|
||||
void PaletteEditor::setRgb(int colorIndex, QRgb rgb) {
|
||||
const int paletteId = this->ui->spinBox_PaletteId->value();
|
||||
|
||||
const int paletteId = currentPaletteId();
|
||||
Tileset *tileset = getTileset(paletteId);
|
||||
tileset->palettes[paletteId][colorIndex] = rgb;
|
||||
tileset->palettePreviews[paletteId][colorIndex] = rgb;
|
||||
|
|
@ -82,21 +102,22 @@ void PaletteEditor::setRgb(int colorIndex, QRgb rgb) {
|
|||
void PaletteEditor::setPalette(int paletteId, const QList<QRgb> &palette) {
|
||||
Tileset *tileset = getTileset(paletteId);
|
||||
for (int i = 0; i < this->numColors; i++) {
|
||||
tileset->palettes[paletteId][i] = palette.at(i);
|
||||
tileset->palettePreviews[paletteId][i] = palette.at(i);
|
||||
tileset->palettes[paletteId][i] = palette.value(i);
|
||||
tileset->palettePreviews[paletteId][i] = palette.value(i);
|
||||
}
|
||||
refreshColorInputs();
|
||||
emit changedPaletteColor();
|
||||
}
|
||||
|
||||
void PaletteEditor::refreshColorInputs() {
|
||||
const int paletteId = ui->spinBox_PaletteId->value();
|
||||
const int paletteId = currentPaletteId();
|
||||
Tileset *tileset = getTileset(paletteId);
|
||||
for (int i = 0; i < this->numColors; i++) {
|
||||
for (int i = 0; i < this->colorInputs.length(); i++) {
|
||||
auto colorInput = this->colorInputs.at(i);
|
||||
const QSignalBlocker b(colorInput);
|
||||
colorInput->setColor(tileset->palettes.at(paletteId).at(i));
|
||||
colorInput->setColor(tileset->palettes.value(paletteId).value(i));
|
||||
}
|
||||
setColorInputTitles(showingUnusedColors());
|
||||
}
|
||||
|
||||
void PaletteEditor::setPaletteId(int paletteId) {
|
||||
|
|
@ -108,6 +129,7 @@ void PaletteEditor::setPaletteId(int paletteId) {
|
|||
void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
|
||||
this->primaryTileset = primaryTileset;
|
||||
this->secondaryTileset = secondaryTileset;
|
||||
this->invalidateCache();
|
||||
this->refreshColorInputs();
|
||||
}
|
||||
|
||||
|
|
@ -120,12 +142,12 @@ void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) {
|
|||
}
|
||||
|
||||
void PaletteEditor::commitEditHistory() {
|
||||
commitEditHistory(ui->spinBox_PaletteId->value());
|
||||
commitEditHistory(currentPaletteId());
|
||||
}
|
||||
|
||||
void PaletteEditor::commitEditHistory(int paletteId) {
|
||||
QList<QRgb> colors;
|
||||
for (int i = 0; i < this->numColors; i++) {
|
||||
for (int i = 0; i < this->colorInputs.length(); i++) {
|
||||
colors.append(this->colorInputs.at(i)->color());
|
||||
}
|
||||
PaletteHistoryItem *commit = new PaletteHistoryItem(colors);
|
||||
|
|
@ -139,24 +161,21 @@ void PaletteEditor::restoreWindowState() {
|
|||
this->restoreState(geometry.value("palette_editor_state"));
|
||||
}
|
||||
|
||||
void PaletteEditor::on_actionUndo_triggered()
|
||||
{
|
||||
int paletteId = this->ui->spinBox_PaletteId->value();
|
||||
void PaletteEditor::on_actionUndo_triggered() {
|
||||
int paletteId = currentPaletteId();
|
||||
PaletteHistoryItem *prev = this->palettesHistory[paletteId].back();
|
||||
if (prev)
|
||||
setPalette(paletteId, prev->colors);
|
||||
}
|
||||
|
||||
void PaletteEditor::on_actionRedo_triggered()
|
||||
{
|
||||
int paletteId = this->ui->spinBox_PaletteId->value();
|
||||
void PaletteEditor::on_actionRedo_triggered() {
|
||||
int paletteId = currentPaletteId();
|
||||
PaletteHistoryItem *next = this->palettesHistory[paletteId].next();
|
||||
if (next)
|
||||
setPalette(paletteId, next->colors);
|
||||
}
|
||||
|
||||
void PaletteEditor::on_actionImport_Palette_triggered()
|
||||
{
|
||||
void PaletteEditor::on_actionImport_Palette_triggered() {
|
||||
QString filepath = FileDialog::getOpenFileName(this, "Import Tileset Palette", "", "Palette Files (*.pal *.act *tpl *gpl)");
|
||||
if (filepath.isEmpty()) {
|
||||
return;
|
||||
|
|
@ -171,11 +190,70 @@ void PaletteEditor::on_actionImport_Palette_triggered()
|
|||
palette.append(0);
|
||||
}
|
||||
|
||||
const int paletteId = ui->spinBox_PaletteId->value();
|
||||
const int paletteId = currentPaletteId();
|
||||
setPalette(paletteId, palette);
|
||||
commitEditHistory(paletteId);
|
||||
}
|
||||
|
||||
void PaletteEditor::invalidateCache() {
|
||||
this->unusedColorCache.clear();
|
||||
if (showingUnusedColors()) {
|
||||
setColorInputTitles(true);
|
||||
}
|
||||
}
|
||||
|
||||
QSet<int> PaletteEditor::getUnusedColorIds() const {
|
||||
const int paletteId = currentPaletteId();
|
||||
|
||||
if (this->unusedColorCache.contains(paletteId)) {
|
||||
return this->unusedColorCache.value(paletteId);
|
||||
}
|
||||
this->unusedColorCache[paletteId] = {};
|
||||
|
||||
// Check our current tilesets for color usage.
|
||||
QSet<int> unusedColorIds = this->primaryTileset->getUnusedColorIds(paletteId, this->secondaryTileset);
|
||||
if (unusedColorIds.isEmpty())
|
||||
return {};
|
||||
unusedColorIds = this->secondaryTileset->getUnusedColorIds(paletteId, this->primaryTileset, unusedColorIds);
|
||||
if (unusedColorIds.isEmpty())
|
||||
return {};
|
||||
|
||||
// The current palette comes from either the primary or secondary tileset.
|
||||
// We need to check all the other tilesets that are paired with the tileset that owns this palette.
|
||||
Tileset *paletteTileset = getTileset(paletteId);
|
||||
QSet<QString> tilesetsToSearch = this->project->getPairedTilesetLabels(paletteTileset);
|
||||
|
||||
// We exclude the currently-loaded pair (we already checked them, and because they're being
|
||||
// edited in the Tileset Editor they may differ from their copies saved in the layout).
|
||||
tilesetsToSearch.remove(this->primaryTileset->name);
|
||||
tilesetsToSearch.remove(this->secondaryTileset->name);
|
||||
|
||||
for (const auto &label : tilesetsToSearch) {
|
||||
Tileset *searchTileset = this->project->getTileset(label);
|
||||
if (!searchTileset) continue;
|
||||
unusedColorIds = searchTileset->getUnusedColorIds(paletteId, paletteTileset, unusedColorIds);
|
||||
if (unusedColorIds.isEmpty())
|
||||
return {};
|
||||
}
|
||||
|
||||
this->unusedColorCache[paletteId] = unusedColorIds;
|
||||
return unusedColorIds;
|
||||
}
|
||||
|
||||
void PaletteEditor::setColorInputTitles(bool showUnused) {
|
||||
porymapConfig.showPaletteEditorUnusedColors = showUnused;
|
||||
|
||||
QSet<int> unusedColorIds = showUnused ? getUnusedColorIds() : QSet<int>();
|
||||
ui->label_AllColorsUsed->setVisible(showUnused && unusedColorIds.isEmpty());
|
||||
for (int i = 0; i < this->colorInputs.length(); i++) {
|
||||
QString title = QString("Color %1").arg(i);
|
||||
if (unusedColorIds.contains(i)) {
|
||||
title.append(QStringLiteral(" (Unused)"));
|
||||
}
|
||||
this->colorInputs.at(i)->setTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditor::closeEvent(QCloseEvent*) {
|
||||
porymapConfig.setPaletteEditorGeometry(
|
||||
this->saveGeometry(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user