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