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