-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathqndsimage.cpp
More file actions
332 lines (275 loc) · 8.69 KB
/
qndsimage.cpp
File metadata and controls
332 lines (275 loc) · 8.69 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#include "qndsimage.h"
QNDSImage::QNDSImage() {}
QNDSImage::QNDSImage(const QImage& img, const QVector<u16>& pal, int alphaThreshold) {
replace(img, pal, alphaThreshold);
}
QNDSImage::QNDSImage(const QImage& img, int colorCount, int alphaThreshold) {
replace(img, colorCount, alphaThreshold);
}
QNDSImage::QNDSImage(const QVector<u8>& ncg, const QVector<u16>& ncl, bool is4bpp) {
replace(ncg, ncl, is4bpp);
}
u16 QNDSImage::toRgb15(u32 rgb24)
{
u8 r, g, b;
r = (rgb24 >> 16) & 0xFF;
g = (rgb24 >> 8) & 0xFF;
b = (rgb24 >> 0) & 0xFF;
r = qRound(r * 31.0 / 255.0);
g = qRound(g * 31.0 / 255.0);
b = qRound(b * 31.0 / 255.0);
return (b << 10) | (g << 5) | r;
}
u32 QNDSImage::toRgb24(u16 rgb15)
{
u8 r, g, b;
r = (rgb15 >> 0) & 0x1F;
g = (rgb15 >> 5) & 0x1F;
b = (rgb15 >> 10) & 0x1F;
r = qRound(r * 255.0 / 31.0);
g = qRound(g * 255.0 / 31.0);
b = qRound(b * 255.0 / 31.0);
return (r << 16) | (g << 8) | b;
}
void QNDSImage::replace(const QImage& img, const QVector<u16>& pal, int alphaThreshold)
{
palette = pal;
const int newPalSize = pal.size();
QVector<QColor> newPal24(newPalSize);
for (int i = 0; i < newPalSize; i++)
newPal24[i] = toRgb24(pal[i]);
const int width = img.width();
const int height = img.height();
texture.resize(width * height);
if(img.depth() == 8 && img.colorCount() <= 16)
{
for (int i = 0, y = 0; y < height; y++)
for (int x = 0; x < width; x++, i++)
texture[i] = img.pixelIndex(x, y);
}
else
{
for (int i = 0, y = 0; y < height; y++)
for (int x = 0; x < width; x++, i++)
texture[i] = closestMatch(img.pixelColor(x, y), newPal24, alphaThreshold);
}
texture = getTiled(width / 8, true);
}
void QNDSImage::replace(const QImage& img, int colorCount, int alphaThreshold)
{
const int width = img.width();
const int height = img.height();
QVector<QColor> pal;
pal.append(QColor(0xFF, 0x00, 0xFF, 0x00)); //Make transparent the first color
if(img.depth() == 8)
{
// We already have a palette, just use it
if(img.colorCount() <= 16) // Valid palette, don't add transparent
pal.clear();
for(const QRgb &color : img.colorTable())
pal.append(color);
}
else
{
// No palette, get all the colors
QColor magenta = QColor(0xFF, 0x00, 0xFF, 0xFF);
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++)
{
QColor c = img.pixelColor(x, y);
if(!pal.contains(c) && c.alpha() >= alphaThreshold && c != magenta)
pal.append(c);
}
}
}
QVector<u16> newPal = createPalette(pal, colorCount);
replace(img, newPal, alphaThreshold);
}
void QNDSImage::replace(const QVector<u8>& ncg, const QVector<u16>& ncl, bool is4bpp)
{
if(is4bpp)
{
const int texSize = ncg.size() << 1;
texture.resize(texSize);
for (int i = 0, j = 0; i < texSize; i += 2, j++)
{
texture[i] = ncg[j] & 0xF;
texture[i + 1] = (ncg[j] >> 4) & 0xF;
}
}
else
texture = ncg;
palette = ncl;
}
QVector<u8> QNDSImage::getTiled(int tileWidth, bool inverse)
{
const int textureSize = texture.size();
const int width = tileWidth * 8;
const int height = textureSize / width;
QVector<u8> out(textureSize);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
{
u32 bf = x + y * width;
u32 tf = bf % 64;
u32 tx = tf % 8;
u32 ty = tf / 8;
u32 gf = bf / 64;
u32 gx = (gf % tileWidth) * 8;
u32 gy = (gf / tileWidth) * 8;
u32 dsti, srci;
if (inverse)
{
dsti = x + y * width;
srci = (gx + tx) + (gy + ty) * width;
}
else
{
dsti = (gx + tx) + (gy + ty) * width;
srci = x + y * width;
}
out[dsti] = texture[srci];
}
}
return out;
}
#define _QNDSIMAGE_PLTT_IMAGE_DEBUG 0
QImage QNDSImage::toImage(int tileWidth)
{
#if _QNDSIMAGE_PLTT_IMAGE_DEBUG
QImage out(16, 16, QImage::Format_ARGB32);
out.fill(Qt::transparent);
for (int i = 0, y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++, i++)
{
if (i == palette.size())
break;
out.setPixelColor(x, y, toRgb24(palette[i]));
}
}
return out;
#else
const int width = tileWidth * 8;
const int height = texture.size() / width;
QImage out(width, height, QImage::Format_Indexed8);
QVector<QRgb> pal;
for(u16 color : this->palette)
pal.append(0xFF000000 | toRgb24(color));
pal[0] &= 0x00FFFFFF; // Make color 0 transparent
out.setColorTable(pal);
QVector<u8> tiled = getTiled(tileWidth, false);
for(int i = 0, y = 0; y < height; y++) {
for(int x = 0; x < width; x++, i++) {
out.setPixel(x, y, tiled[i]);
}
}
return out;
#endif
}
void QNDSImage::toNitro(QVector<u8>& ncg, QVector<u16>& ncl, bool is4bpp)
{
if(is4bpp)
{
const int texSize = texture.size() >> 1;
ncg.resize(texSize);
for (int i = 0, j = 0; i < texSize; i++, j += 2)
{
ncg[i] = texture[j] & 0xF;
ncg[i] |= (texture[j + 1] & 0xF) << 4;
}
}
else
ncg = texture;
ncl = palette;
}
QVector<u16> QNDSImage::createPalette(QVector<QColor> pal, int colorCount)
{
if(pal.size() < colorCount)
pal.resize(colorCount);
// Sort colors and reduce to needed amount
if(pal.size() > colorCount)
{
// For finding color channel that has the most wide range,
// we need to keep their lower and upper bound.
int lower_red = pal[0].red(),
lower_green = pal[0].green(),
lower_blue = pal[0].blue();
int upper_red = 0,
upper_green = 0,
upper_blue = 0;
// Loop trough all the colors
for (QColor c : pal)
{
lower_red = std::min(lower_red, c.red());
lower_green = std::min(lower_green, c.green());
lower_blue = std::min(lower_blue, c.blue());
upper_red = std::max(upper_red, c.red());
upper_green = std::max(upper_green, c.green());
upper_blue = std::max(upper_blue, c.blue());
}
int red = upper_red - lower_red;
int green = upper_green - lower_green;
int blue = upper_blue - lower_blue;
int max = std::max(std::max(red, green), blue);
// Compare two rgb color according to our selected color channel.
std::sort(pal.begin(), pal.end(),
[max, red, green/*, blue*/](const QColor& c1, const QColor& c2)
{
// Always keep transparent at the start
if(c1.alpha() == 0)
return true;
else if(c2.alpha() == 0)
return false;
if (max == red) // if red is our color that has the widest range
return c1.red() < c2.red(); // just compare their red channel
else if (max == green) //...
return c1.green() < c2.green();
else //if (max == blue)
return c1.blue() < c2.blue();
});
// Reduce to the desired number of colors
double groupSize = pal.size() / colorCount;
for (int i = 0; i < colorCount; ++i)
palette.append(toRgb15(pal[qRound((groupSize * i) + (groupSize / 2))].rgb()));
}
else
{
// Already a valid palette, just use it
for (int i = 0; i < colorCount; ++i) {
palette.append(toRgb15(pal[i].rgb()));
}
}
return palette;
}
inline int QNDSImage::pixelDistance(QColor p1, QColor p2)
{
int r1 = p1.red();
int g1 = p1.green();
int b1 = p1.blue();
int a1 = p1.alpha();
int r2 = p2.red();
int g2 = p2.green();
int b2 = p2.blue();
int a2 = p2.alpha();
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2) + abs(a1 - a2);
}
inline int QNDSImage::closestMatch(QColor pixel, const QVector<QColor>& clut, int alphaThreshold)
{
// If it's transparency, index 0
if(pixel.alpha() < alphaThreshold || pixel == QColor(0xFF, 0x00, 0xFF, 0xFF)) {
return 0;
}
// Otherwise find the closest match
int idx = 0;
int current_distance = INT_MAX;
for (int i = 1; i < clut.size(); ++i)
{
int dist = pixelDistance(pixel, clut[i]);
if (dist < current_distance)
{
current_distance = dist;
idx = i;
}
}
return idx;
}