all repos — mgba @ ae60489d990dfaf413846958aa08c971ff6ecaf3

mGBA Game Boy Advance Emulator

Qt: Add export capability for sprites
Vicki Pfau vi@endrift.com
Thu, 02 Feb 2017 16:34:18 -0800
commit

ae60489d990dfaf413846958aa08c971ff6ecaf3

parent

f3b66397a2a598e5cfb63acd0c0b84f5796d0802

M src/platform/qt/AssetView.cppsrc/platform/qt/AssetView.cpp

@@ -7,6 +7,8 @@ #include "AssetView.h"

#include <QTimer> +#include <mgba/core/tile-cache.h> + using namespace QGBA; AssetView::AssetView(GameController* controller, QWidget* parent)

@@ -51,3 +53,47 @@

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; + } +}
M src/platform/qt/AssetView.hsrc/platform/qt/AssetView.h

@@ -18,6 +18,8 @@

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);
M src/platform/qt/ObjView.cppsrc/platform/qt/ObjView.cpp

@@ -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 @@ connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int)));

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 @@ m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());

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 @@ unsigned tile = obj->tile;

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 @@ palette = GBObjAttributesGetCGBPalette(obj->attr);

} 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 @@ m_objInfo = newInfo;

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 @@ m_ui.mode->setText(tr("N/A"));

} #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; }
M src/platform/qt/ObjView.hsrc/platform/qt/ObjView.h

@@ -21,6 +21,9 @@

public: ObjView(GameController* controller, QWidget* parent = nullptr); +public slots: + void exportObj(); + private slots: void selectObj(int); void translateIndex(int);

@@ -43,6 +46,9 @@ unsigned tile;

unsigned width; unsigned height; unsigned stride; + unsigned paletteId; + unsigned paletteSet; + unsigned bits; bool operator!=(const ObjInfo&); } m_objInfo;
M src/platform/qt/ObjView.uisrc/platform/qt/ObjView.ui

@@ -7,7 +7,7 @@ <rect>

<x>0</x> <y>0</y> <width>454</width> - <height>375</height> + <height>385</height> </rect> </property> <property name="windowTitle">

@@ -67,6 +67,13 @@ <item>

<widget class="QLabel" name="label_2"> <property name="text"> <string>Magnification</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="exportButton"> + <property name="text"> + <string>Export</string> </property> </widget> </item>