-
Notifications
You must be signed in to change notification settings - Fork 1
/
spriteblock.h
159 lines (147 loc) · 5.87 KB
/
spriteblock.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#ifndef SPRITEBLOCK_H
#define SPRITEBLOCK_H
#include "rom.h"
#include <QByteArray>
#include <string.h>
#include <assert.h>
#include "abstracttile.h"
struct SpriteBlock : public AbstractTile
{
virtual ~SpriteBlock() {}
virtual bool setPixels(const QByteArray& newpixels)
{
assert(size==8 || size==16);
size_t subwidth = size/8;
size_t subblocks = subwidth*subwidth;
size_t uncompressedSize = 8*8*subblocks/2;
uint8_t* d = NULL; // uncompressed snes-formatted data
uint8_t* uncompressedData = new uint8_t[uncompressedSize]; // 4bpp
if (compressed) {
d = uncompressedData;
} else {
d = data;
}
memset(d, 0, uncompressedSize);
int n=0;
for (size_t l=0; l<subwidth; l++) { // 1 or 2 8x8 blocks in Y
for (size_t k=0; k<8; k++) { // 8 rows
for (size_t j=0; j<subwidth; j++) { // 1 or 2 8x8 blocks in X
// 8 columns
for (int i=7; i>=0; i--) {
uint8_t p = newpixels[n];
if (p&1) d[(j+2*l)*32+k*2+0] |= (1<<i);
if (p&2) d[(j+2*l)*32+k*2+1] |= (1<<i);
if (p&4) d[(j+2*l)*32+k*2+16] |= (1<<i);
if (p&8) d[(j+2*l)*32+k*2+17] |= (1<<i);
n++;
}
}
}
}
if (compressed) {
QByteArray compressedData; // TODO: remove qt dependency
int extraWords = 0;
do {
compressedData.clear();
for (size_t i=0; i<uncompressedSize/16; i++) {
uint8_t b=0;
for (size_t j=0; j<8; j++)
if (!(d[i*16+2*j] || d[i*16+2*j+1])) b |= (1<<j); // 1 status bit per word of data, 1=compress-away
for (size_t j=0; j<8; j++)
if (extraWords>0 && (b & (1<<j))) { b &= ~(1<<j); extraWords--; } // don't compress away to fill original size
compressedData.append(b); // append 8 status bits as byte
for (size_t j=0; j<8; j++) // append words that are not compressed away
if (!(b & (1<<j))) { compressedData.append(d[i*16+2*j]); compressedData.append(d[i*16+2*j+1]); }
}
extraWords = ((int)romsize - (int)compressedData.size())/2; // additional words to NOT compress-away to fill original size
} while (extraWords > 0);
if ((unsigned)compressedData.size() > romsize) return false; // won't fit
memset(data, 0, romsize); // clear free data in case we shrink it
memcpy(data, compressedData.data(), compressedData.size());
}
pixels = newpixels;
delete[] uncompressedData;
return true;
}
private:
static QByteArray loadPixels(const uint8_t* data, bool compressed, const uint8_t** next=NULL, int size=16)
{
assert(size == 8 || size == 16);
size_t subwidth = size/8;
size_t subblocks = subwidth*subwidth;
size_t uncompressedSize = 8*8*subblocks/2;
QByteArray res;
const uint8_t* d = NULL;
uint8_t* uncompressed = new uint8_t[uncompressedSize]; // 4bpp
if (compressed) {
memset(uncompressed, 0, uncompressedSize);
const uint8_t* din = data;
uint8_t* dout = uncompressed;
for (size_t i=0; i<uncompressedSize/2/8; i++) { // 1 bit per output word
uint8_t bits = *din; din++;
for (uint8_t bp=0; bp<8; bp++) {
uint8_t bit = bits&1; bits>>=1;
if (!bit) { // copy word as-is
*dout = *din; din++; dout++;
*dout = *din; din++; dout++;
} else { // word is zero
dout += 2;
}
}
}
d = uncompressed;
if (next) *next = din;
}
else {
d = data;
if (next) *next = data+uncompressedSize;
}
for (size_t l=0; l<subwidth; l++) { // 1 or 2 8x8 blocks in Y
for (size_t k=0; k<8; k++) { // 8 rows
for (size_t j=0; j<subwidth; j++) { // 1 or 2 8x8 blocks in X
// 8 columns
for (int i=7; i>=0; i--) {
uint8_t p=0;
p |= (d[(j+2*l)*32+k*2+0]&(1<<i)) ? 1 : 0;
p |= (d[(j+2*l)*32+k*2+1]&(1<<i)) ? 2 : 0;
p |= (d[(j+2*l)*32+k*2+16]&(1<<i)) ? 4 : 0;
p |= (d[(j+2*l)*32+k*2+17]&(1<<i)) ? 8 : 0;
res.append(p);
}
}
}
}
delete[] uncompressed;
return res;
}
public:
SpriteBlock(int i, Rom* rom, int size=16) {
this->i = i;
this->size = size;
if (size==16) { // 16x16 blocks
ptraddr = 0xec0000 + i*3;
dataaddr = rom->read24(ptraddr);
compressed = (dataaddr>>23);
dataaddr &= ~(1<<23);
dataaddr += 0xd90000;
} else if (size==8) { // 8x8 blocks
ptraddr = 0xd80000 + i*3;
dataaddr = rom->read24(ptraddr);
compressed = (dataaddr>>23);
dataaddr &= ~(1<<23);
dataaddr += 0xd10000;
} else {
assert(false);
}
romsize = 0;
if (compressed) rom->readBlock(this->dataaddr, data, size==16 ? sizeof(data) : sizeof(data)/4);
else rom->readBlock(dataaddr, data, size==16?128:128/4);
const uint8_t* next = data;
pixels = loadPixels(data, compressed, &next, size);
romsize = next-data;
}
virtual bool save(Rom* rom) const {
return rom->writeBlock(dataaddr, data, romsize);
}
};
#endif // SPRITEBLOCK_H