src/platform/qt/ObjView.cpp (view raw)
1/* Copyright (c) 2013-2016 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "ObjView.h"
7
8#include "GBAApp.h"
9
10#include <QFontDatabase>
11#include <QTimer>
12
13#include "LogController.h"
14#include "VFileDevice.h"
15
16#ifdef M_CORE_GBA
17#include <mgba/internal/gba/gba.h>
18#endif
19#ifdef M_CORE_GB
20#include <mgba/internal/gb/gb.h>
21#include <mgba/internal/gb/io.h>
22#endif
23#include <mgba-util/png-io.h>
24
25using namespace QGBA;
26
27ObjView::ObjView(GameController* controller, QWidget* parent)
28 : AssetView(controller, parent)
29 , m_controller(controller)
30 , m_tileStatus{}
31 , m_objId(0)
32 , m_objInfo{}
33{
34 m_ui.setupUi(this);
35 m_ui.tile->setController(controller);
36
37 const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
38
39 m_ui.x->setFont(font);
40 m_ui.y->setFont(font);
41 m_ui.w->setFont(font);
42 m_ui.h->setFont(font);
43 m_ui.address->setFont(font);
44 m_ui.priority->setFont(font);
45 m_ui.palette->setFont(font);
46 m_ui.transform->setFont(font);
47 m_ui.mode->setFont(font);
48
49 connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(translateIndex(int)));
50 connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int)));
51 connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
52 updateTiles(true);
53 });
54 connect(m_ui.exportButton, SIGNAL(clicked()), this, SLOT(exportObj()));
55}
56
57void ObjView::selectObj(int obj) {
58 m_objId = obj;
59 updateTiles(true);
60}
61
62void ObjView::translateIndex(int index) {
63 unsigned x = index % m_objInfo.width;
64 unsigned y = index / m_objInfo.width;
65 m_ui.tile->selectIndex(x + y * m_objInfo.stride + m_tileOffset);
66}
67
68#ifdef M_CORE_GBA
69void ObjView::updateTilesGBA(bool force) {
70 const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
71 const GBAObj* obj = &gba->video.oam.obj[m_objId];
72
73 unsigned shape = GBAObjAttributesAGetShape(obj->a);
74 unsigned size = GBAObjAttributesBGetSize(obj->b);
75 unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
76 unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
77 unsigned tile = GBAObjAttributesCGetTile(obj->c);
78 m_ui.tiles->setTileCount(width * height / 64);
79 m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
80 m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
81 unsigned palette = GBAObjAttributesCGetPalette(obj->c);
82 unsigned tileBase = tile;
83 unsigned paletteSet;
84 unsigned bits;
85 if (GBAObjAttributesAIs256Color(obj->a)) {
86 m_ui.palette->setText("256-color");
87 paletteSet = 1;
88 m_ui.tile->setPalette(0);
89 m_ui.tile->setPaletteSet(1, 1024, 1536);
90 palette = 1;
91 tile = tile / 2 + 1024;
92 bits = 8;
93 } else {
94 m_ui.palette->setText(QString::number(palette));
95 paletteSet = 0;
96 m_ui.tile->setPalette(palette);
97 m_ui.tile->setPaletteSet(0, 2048, 3072);
98 palette += 16;
99 tile += 2048;
100 bits = 4;
101 }
102 ObjInfo newInfo{
103 tile,
104 width / 8,
105 height / 8,
106 width / 8,
107 palette,
108 paletteSet,
109 bits
110 };
111 if (newInfo != m_objInfo) {
112 force = true;
113 }
114 GBARegisterDISPCNT dispcnt = gba->memory.io[0]; // FIXME: Register name can't be imported due to namespacing issues
115 if (!GBARegisterDISPCNTIsObjCharacterMapping(dispcnt)) {
116 newInfo.stride = 0x20 >> (GBAObjAttributesAGet256Color(obj->a));
117 };
118 m_objInfo = newInfo;
119 m_tileOffset = tile;
120 mTileCacheSetPalette(m_tileCache.get(), paletteSet);
121
122 int i = 0;
123 for (int y = 0; y < height / 8; ++y) {
124 for (int x = 0; x < width / 8; ++x, ++i, ++tile, ++tileBase) {
125 const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * tileBase], tile, palette);
126 if (data) {
127 m_ui.tiles->setTile(i, data);
128 } else if (force) {
129 m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), tile, palette));
130 }
131 }
132 tile += newInfo.stride - width / 8;
133 tileBase += newInfo.stride - width / 8;
134 }
135
136 m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
137 m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
138 m_ui.w->setText(QString::number(width));
139 m_ui.h->setText(QString::number(height));
140
141 m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
142 m_ui.priority->setText(QString::number(GBAObjAttributesCGetPriority(obj->c)));
143 m_ui.flippedH->setChecked(GBAObjAttributesBIsHFlip(obj->b));
144 m_ui.flippedV->setChecked(GBAObjAttributesBIsVFlip(obj->b));
145 m_ui.enabled->setChecked(!GBAObjAttributesAIsDisable(obj->a) || GBAObjAttributesAIsTransformed(obj->a));
146 m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
147 m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
148
149 if (GBAObjAttributesAIsTransformed(obj->a)) {
150 m_ui.transform->setText(QString::number(GBAObjAttributesBGetMatIndex(obj->b)));
151 } else {
152 m_ui.transform->setText(tr("Off"));
153 }
154
155 switch (GBAObjAttributesAGetMode(obj->a)) {
156 case OBJ_MODE_NORMAL:
157 m_ui.mode->setText(tr("Normal"));
158 break;
159 case OBJ_MODE_SEMITRANSPARENT:
160 m_ui.mode->setText(tr("Trans"));
161 break;
162 case OBJ_MODE_OBJWIN:
163 m_ui.mode->setText(tr("OBJWIN"));
164 break;
165 default:
166 m_ui.mode->setText(tr("Invalid"));
167 break;
168 }
169}
170#endif
171
172#ifdef M_CORE_GB
173void ObjView::updateTilesGB(bool force) {
174 const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
175 const GBObj* obj = &gb->video.oam.obj[m_objId];
176
177 unsigned width = 8;
178 unsigned height = 8;
179 GBRegisterLCDC lcdc = gb->memory.io[REG_LCDC];
180 if (GBRegisterLCDCIsObjSize(lcdc)) {
181 height = 16;
182 }
183 unsigned tile = obj->tile;
184 m_ui.tiles->setTileCount(width * height / 64);
185 m_ui.tiles->setMinimumSize(QSize(width, height) * m_ui.magnification->value());
186 m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
187 unsigned palette = 0;
188 if (gb->model >= GB_MODEL_CGB) {
189 if (GBObjAttributesIsBank(obj->attr)) {
190 tile += 512;
191 }
192 palette = GBObjAttributesGetCGBPalette(obj->attr);
193 } else {
194 palette = GBObjAttributesGetPalette(obj->attr);
195 }
196 m_ui.palette->setText(QString::number(palette));
197 palette += 8;
198
199 ObjInfo newInfo{
200 tile,
201 1,
202 height / 8,
203 1,
204 palette,
205 0,
206 2
207 };
208 if (newInfo != m_objInfo) {
209 force = true;
210 }
211 m_objInfo = newInfo;
212 m_tileOffset = tile;
213
214 int i = 0;
215 mTileCacheSetPalette(m_tileCache.get(), 0);
216 m_ui.tile->setPalette(palette);
217 m_ui.tile->setPaletteSet(0, 512, 1024);
218 for (int y = 0; y < height / 8; ++y, ++i) {
219 unsigned t = tile + i;
220 const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[16 * t], t, palette);
221 if (data) {
222 m_ui.tiles->setTile(i, data);
223 } else if (force) {
224 m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t, palette));
225 }
226 }
227
228 m_ui.x->setText(QString::number(obj->x));
229 m_ui.y->setText(QString::number(obj->y));
230 m_ui.w->setText(QString::number(width));
231 m_ui.h->setText(QString::number(height));
232
233 m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
234 m_ui.priority->setText(QString::number(GBObjAttributesGetPriority(obj->attr)));
235 m_ui.flippedH->setChecked(GBObjAttributesIsXFlip(obj->attr));
236 m_ui.flippedV->setChecked(GBObjAttributesIsYFlip(obj->attr));
237 m_ui.enabled->setChecked(obj->y != 0 && obj->y < 160);
238 m_ui.doubleSize->setChecked(false);
239 m_ui.mosaic->setChecked(false);
240 m_ui.transform->setText(tr("N/A"));
241 m_ui.mode->setText(tr("N/A"));
242}
243#endif
244
245void ObjView::exportObj() {
246 GameController::Interrupter interrupter(m_controller);
247 QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export sprite"),
248 tr("Portable Network Graphics (*.png)"));
249 if (!dialog->exec()) {
250 return;
251 }
252 QString filename = dialog->selectedFiles()[0];
253 VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
254 if (!vf) {
255 LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
256 return;
257 }
258
259 mTileCacheSetPalette(m_tileCache.get(), m_objInfo.paletteSet);
260 png_structp png = PNGWriteOpen(vf);
261 png_infop info = PNGWriteHeader8(png, m_objInfo.width * 8, m_objInfo.height * 8);
262
263 const uint16_t* rawPalette = mTileCacheGetPalette(m_tileCache.get(), m_objInfo.paletteId);
264 unsigned colors = 1 << m_objInfo.bits;
265 uint32_t palette[256];
266 for (unsigned c = 0; c < colors && c < 256; ++c) {
267 uint16_t color = rawPalette[c];
268 palette[c] = M_R8(rawPalette[c]);
269 palette[c] |= M_G8(rawPalette[c]) << 8;
270 palette[c] |= M_B8(rawPalette[c]) << 16;
271 if (c) {
272 palette[c] |= 0xFF000000;
273 }
274 }
275 PNGWritePalette(png, info, palette, colors);
276
277 uint8_t* buffer = new uint8_t[m_objInfo.width * m_objInfo.height * 8 * 8];
278 unsigned t = m_objInfo.tile;
279 for (int y = 0; y < m_objInfo.height; ++y) {
280 for (int x = 0; x < m_objInfo.width; ++x, ++t) {
281 compositeTile(t, static_cast<void*>(buffer), m_objInfo.width * 8, x * 8, y * 8, m_objInfo.bits);
282 }
283 t += m_objInfo.stride - m_objInfo.width;
284 }
285 PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));
286 PNGWriteClose(png, info);
287 delete[] buffer;
288}
289
290bool ObjView::ObjInfo::operator!=(const ObjInfo& other) {
291 return other.tile != tile ||
292 other.width != width ||
293 other.height != height ||
294 other.stride != stride ||
295 other.paletteId != paletteId ||
296 other.paletteSet != paletteSet;
297}