mirror of https://github.com/mgba-emu/mgba.git
Qt: Add export capability for sprites
This commit is contained in:
parent
f3b66397a2
commit
ae60489d99
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <QTimer>
|
||||
|
||||
#include <mgba/core/tile-cache.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AssetView::AssetView(GameController* controller, QWidget* parent)
|
||||
|
@ -51,3 +53,47 @@ void AssetView::resizeEvent(QResizeEvent*) {
|
|||
void AssetView::showEvent(QShowEvent*) {
|
||||
updateTiles(true);
|
||||
}
|
||||
|
||||
void AssetView::compositeTile(unsigned tileId, void* buffer, size_t stride, size_t x, size_t y, int depth) {
|
||||
const uint8_t* tile = mTileCacheGetRawTile(m_tileCache.get(), tileId);
|
||||
uint8_t* pixels = static_cast<uint8_t*>(buffer);
|
||||
size_t base = stride * y + x;
|
||||
switch (depth) {
|
||||
case 2:
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
uint8_t tileDataLower = tile[i * 2];
|
||||
uint8_t tileDataUpper = tile[i * 2 + 1];
|
||||
uint8_t pixel;
|
||||
pixel = ((tileDataUpper & 128) >> 6) | ((tileDataLower & 128) >> 7);
|
||||
pixels[base + i * stride] = pixel;
|
||||
pixel = ((tileDataUpper & 64) >> 5) | ((tileDataLower & 64) >> 6);
|
||||
pixels[base + i * stride + 1] = pixel;
|
||||
pixel = ((tileDataUpper & 32) >> 4) | ((tileDataLower & 32) >> 5);
|
||||
pixels[base + i * stride + 2] = pixel;
|
||||
pixel = ((tileDataUpper & 16) >> 3) | ((tileDataLower & 16) >> 4);
|
||||
pixels[base + i * stride + 3] = pixel;
|
||||
pixel = ((tileDataUpper & 8) >> 2) | ((tileDataLower & 8) >> 3);
|
||||
pixels[base + i * stride + 4] = pixel;
|
||||
pixel = ((tileDataUpper & 4) >> 1) | ((tileDataLower & 4) >> 2);
|
||||
pixels[base + i * stride + 5] = pixel;
|
||||
pixel = (tileDataUpper & 2) | ((tileDataLower & 2) >> 1);
|
||||
pixels[base + i * stride + 6] = pixel;
|
||||
pixel = ((tileDataUpper & 1) << 1) | (tileDataLower & 1);
|
||||
pixels[base + i * stride + 7] = pixel;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
for (size_t j = 0; j < 8; ++j) {
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
pixels[base + j * stride + i * 2] = tile[j * 4 + i] & 0xF;
|
||||
pixels[base + j * stride + i * 2 + 1] = tile[j * 4 + i] >> 4;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
memcpy(&pixels[base + i * stride], &tile[i * 8], 8);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ Q_OBJECT
|
|||
public:
|
||||
AssetView(GameController* controller, QWidget* parent = nullptr);
|
||||
|
||||
void compositeTile(unsigned tileId, void* image, size_t stride, size_t x, size_t y, int depth = 8);
|
||||
|
||||
protected slots:
|
||||
void updateTiles(bool force = false);
|
||||
|
||||
|
|
|
@ -10,11 +10,17 @@
|
|||
#include <QFontDatabase>
|
||||
#include <QTimer>
|
||||
|
||||
#include "LogController.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#endif
|
||||
#include <mgba-util/png-io.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -45,6 +51,7 @@ ObjView::ObjView(GameController* controller, QWidget* parent)
|
|||
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
|
||||
updateTiles(true);
|
||||
});
|
||||
connect(m_ui.exportButton, SIGNAL(clicked()), this, SLOT(exportObj()));
|
||||
}
|
||||
|
||||
void ObjView::selectObj(int obj) {
|
||||
|
@ -73,36 +80,44 @@ void ObjView::updateTilesGBA(bool force) {
|
|||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
||||
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
|
||||
unsigned tileBase = tile;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
if (GBAObjAttributesAIs256Color(obj->a)) {
|
||||
m_ui.palette->setText("256-color");
|
||||
mTileCacheSetPalette(m_tileCache.get(), 1);
|
||||
paletteSet = 1;
|
||||
m_ui.tile->setPalette(0);
|
||||
m_ui.tile->setPaletteSet(1, 1024, 1536);
|
||||
palette = 1;
|
||||
tile = tile / 2 + 1024;
|
||||
bits = 8;
|
||||
} else {
|
||||
m_ui.palette->setText(QString::number(palette));
|
||||
mTileCacheSetPalette(m_tileCache.get(), 0);
|
||||
paletteSet = 0;
|
||||
m_ui.tile->setPalette(palette);
|
||||
m_ui.tile->setPaletteSet(0, 2048, 3072);
|
||||
palette += 16;
|
||||
tile += 2048;
|
||||
bits = 4;
|
||||
}
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
width / 8,
|
||||
height / 8,
|
||||
width / 8
|
||||
width / 8,
|
||||
palette,
|
||||
paletteSet,
|
||||
bits
|
||||
};
|
||||
if (newInfo != m_objInfo) {
|
||||
force = true;
|
||||
}
|
||||
m_objInfo = newInfo;
|
||||
m_tileOffset = tile;
|
||||
GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
|
||||
if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
|
||||
newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
|
||||
};
|
||||
m_objInfo = newInfo;
|
||||
m_tileOffset = tile;
|
||||
mTileCacheSetPalette(m_tileCache.get(), paletteSet);
|
||||
|
||||
int i = 0;
|
||||
for (int y = 0; y < height / 8; ++y) {
|
||||
|
@ -169,7 +184,7 @@ void ObjView::updateTilesGB(bool force) {
|
|||
m_ui.tiles->setTileCount(width * height / 64);
|
||||
m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
|
||||
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
|
||||
int palette = 0;
|
||||
unsigned palette = 0;
|
||||
if (gb->model >= GB_MODEL_CGB) {
|
||||
if (GBObjAttributesIsBank(obj->attr)) {
|
||||
tile += 512;
|
||||
|
@ -178,11 +193,17 @@ void ObjView::updateTilesGB(bool force) {
|
|||
} else {
|
||||
palette = GBObjAttributesGetPalette(obj->attr);
|
||||
}
|
||||
m_ui.palette->setText(QString::number(palette));
|
||||
palette += 8;
|
||||
|
||||
ObjInfo newInfo{
|
||||
tile,
|
||||
1,
|
||||
height / 8,
|
||||
1
|
||||
1,
|
||||
palette,
|
||||
0,
|
||||
2
|
||||
};
|
||||
if (newInfo != m_objInfo) {
|
||||
force = true;
|
||||
|
@ -191,8 +212,6 @@ void ObjView::updateTilesGB(bool force) {
|
|||
m_tileOffset = tile;
|
||||
|
||||
int i = 0;
|
||||
m_ui.palette->setText(QString::number(palette));
|
||||
palette += 8;
|
||||
mTileCacheSetPalette(m_tileCache.get(), 0);
|
||||
m_ui.tile->setPalette(palette);
|
||||
m_ui.tile->setPaletteSet(0, 512, 1024);
|
||||
|
@ -223,10 +242,56 @@ void ObjView::updateTilesGB(bool force) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void ObjView::exportObj() {
|
||||
GameController::Interrupter interrupter(m_controller);
|
||||
QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export sprite"),
|
||||
tr("Portable Network Graphics (*.png)"));
|
||||
if (!dialog->exec()) {
|
||||
return;
|
||||
}
|
||||
QString filename = dialog->selectedFiles()[0];
|
||||
VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (!vf) {
|
||||
LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
mTileCacheSetPalette(m_tileCache.get(), m_objInfo.paletteSet);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
|
||||
|
||||
const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache.get(), m_objInfo.paletteId);
|
||||
unsigned colors = 1 << m_objInfo.bits;
|
||||
uint32_t palette[256];
|
||||
for (unsigned c = 0; c < colors && c < 256; ++c) {
|
||||
uint16_t color = rawPalette[c];
|
||||
palette[c] = M_R8(rawPalette[c]);
|
||||
palette[c] |= M_G8(rawPalette[c]) << 8;
|
||||
palette[c] |= M_B8(rawPalette[c]) << 16;
|
||||
if (c) {
|
||||
palette[c] |= 0xFF000000;
|
||||
}
|
||||
}
|
||||
PNGWritePalette(png, info, palette, colors);
|
||||
|
||||
uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8];
|
||||
unsigned t = m_objInfo.tile;
|
||||
for (int y = 0; y < m_objInfo.height; ++y) {
|
||||
for (int x = 0; x < m_objInfo.width; ++x, ++t) {
|
||||
compositeTile(t, static_cast<void*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits);
|
||||
}
|
||||
t += m_objInfo.stride - m_objInfo.width;
|
||||
}
|
||||
PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));
|
||||
PNGWriteClose(png, info);
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
bool ObjView::ObjInfo::operator!=(const ObjInfo& other) {
|
||||
return other.tile != tile ||
|
||||
other.width != width ||
|
||||
other.height != height ||
|
||||
other.stride != stride;
|
||||
other.stride != stride ||
|
||||
other.paletteId != paletteId ||
|
||||
other.paletteSet != paletteSet;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ Q_OBJECT
|
|||
public:
|
||||
ObjView(GameController* controller, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void exportObj();
|
||||
|
||||
private slots:
|
||||
void selectObj(int);
|
||||
void translateIndex(int);
|
||||
|
@ -43,6 +46,9 @@ private:
|
|||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned stride;
|
||||
unsigned paletteId;
|
||||
unsigned paletteSet;
|
||||
unsigned bits;
|
||||
|
||||
bool operator!=(const ObjInfo&);
|
||||
} m_objInfo;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>454</width>
|
||||
<height>375</height>
|
||||
<height>385</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -70,6 +70,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="exportButton">
|
||||
<property name="text">
|
||||
<string>Export</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
|
|
Loading…
Reference in New Issue