/
image_formats.cpp
368 lines (318 loc) · 12 KB
/
image_formats.cpp
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/** Copyright (C) 2008-2011 Josh Ventura
*** Copyright (C) 2013 Ssss
*** Copyright (C) 2013-2014 Robert B. Colton
***
*** This file is a part of the ENIGMA Development Environment.
***
*** ENIGMA is free software: you can redistribute it and/or modify it under the
*** terms of the GNU General Public License as published by the Free Software
*** Foundation, version 3 of the license or any later version.
***
*** This application and its source code is distributed AS-IS, WITHOUT ANY
*** WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
*** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
*** details.
***
*** You should have received a copy of the GNU General Public License along
*** with this code. If not, see <http://www.gnu.org/licenses/>
**/
#include "image_formats.h"
#include "image_formats_exts.h"
#include "Universal_System/estring.h"
#include "Widget_Systems/widgets_mandatory.h"
#include "gif_format.h"
#include <map>
#include <fstream> // std::ofstream
#include <sstream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
using namespace std;
#include "nlpo2.h"
inline unsigned int lgpp2(unsigned int x){//Trailing zero count. lg for perfect powers of two
x = (x & -x) - 1;
x -= ((x >> 1) & 0x55555555);
x = ((x >> 2) & 0x33333333) + (x & 0x33333333);
x = ((x >> 4) + x) & 0x0f0f0f0f;
x += x >> 8;
return (x + (x >> 16)) & 63;
}
namespace enigma
{
std::map<std::string, ImageLoadFunction> image_load_handlers;
std::map<std::string, ImageSaveFunction> image_save_handlers;
void image_add_loader(std::string format, ImageLoadFunction fnc) {
image_load_handlers[format] = fnc;
}
void image_add_saver(std::string format, ImageSaveFunction fnc) {
image_save_handlers[format] = fnc;
}
unsigned char* image_flip(const unsigned char* data, unsigned width, unsigned height, unsigned bytes) {
//flipped upside down
unsigned sz = width * height;
unsigned char* rgbdata = new unsigned char[sz * bytes];
for (unsigned int i = 0; i < height; i++) { // Doesn't matter the order now
memcpy(&rgbdata[i*width*bytes*sizeof(unsigned char)], // address of destination
&data[(height-i-1)*width*bytes*sizeof(unsigned char)], // address of source
width*bytes*sizeof(unsigned char) ); // number of bytes to copy
}
return rgbdata;
}
string image_get_format(string filename) {
size_t fp = filename.find_last_of(".");
if (fp == string::npos){
return "";
}
string ext = filename.substr(fp);
transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return ext;
}
/// Generic all-purpose image loading call.
unsigned char* image_load(string filename, string format, unsigned int* width, unsigned int* height, unsigned int* fullwidth, unsigned int* fullheight, int* imgnumb, bool flipped) {
if (format.compare(".bmp") == 0) {
return image_load_bmp(filename, width, height, fullwidth, fullheight, flipped);
} else if (format.compare(".gif") == 0) {
return image_load_gif(filename, width, height, fullwidth, fullheight, imgnumb, flipped);
}
auto handler = image_load_handlers.find(format);
if (handler != image_load_handlers.end()) {
return (*handler).second(filename, width, height, fullwidth, fullheight, flipped);
}
return image_load_bmp(filename, width, height, fullwidth, fullheight, flipped);
}
/// Generic all-purpose image loading call that will regexp the filename for the format and call the appropriate function.
unsigned char* image_load(string filename, unsigned int* width, unsigned int* height, unsigned int* fullwidth, unsigned int* fullheight, int* imgnumb, bool flipped) {
string format = image_get_format(filename);
if (format.empty()) {
format = ".bmp";
}
return image_load(filename, format, width, height, fullwidth, fullheight, imgnumb, flipped);
}
/// Generic all-purpose image saving call.
int image_save(string filename, const unsigned char* data, string format, unsigned width, unsigned height, unsigned fullwidth, unsigned fullheight, bool flipped) {
if (format.compare(".bmp") == 0) {
return image_save_bmp(filename, data, width, height, fullwidth, fullheight, flipped);
}
auto handler = image_save_handlers.find(format);
if (handler != image_save_handlers.end()) {
return (*handler).second(filename, data, width, height, fullwidth, fullheight, flipped);
}
return image_save_bmp(filename, data, width, height, fullwidth, fullheight, flipped);
}
/// Generic all-purpose image saving call that will regexp the filename for the format and call the appropriate function.
int image_save(string filename, const unsigned char* data, unsigned width, unsigned height, unsigned fullwidth, unsigned fullheight, bool flipped) {
string format = image_get_format(filename);
if (format.empty()) {
format = ".bmp";
}
return image_save(filename, data, format, width, height, fullwidth, fullheight, flipped);
}
unsigned char* image_load_bmp(
string filename, unsigned int* width, unsigned int* height,
unsigned int* fullwidth, unsigned int* fullheight, bool flipped) {
if (std::ifstream bmp{filename}) {
std::stringstream buffer;
buffer << bmp.rdbuf();
return image_decode_bmp(buffer.str(), width, height,
fullwidth, fullheight, flipped);
}
return nullptr;
}
namespace {
#pragma pack(push, whatever)
#pragma pack(2)
enum class BitmapCompression: uint32_t {
RGB = 0x00,
RLE8 = 0x01,
RLE4 = 0x02,
BITFIELDS = 0x03,
JPEG = 0x04,
PNG = 0x05,
CMYK = 0x0B,
CMYKRLE8 = 0x0C,
CMYKRLE4 = 0x0D
};
enum class LogicalColorSpace: uint32_t {
CALIBRATED_RGB = 0, // Invokes the wrath of the calibration vectors below
sRGB = 0x73524742, // 'sRGB', big endian
WINDOWS = 0x57696E20 // 'Win ', big endian (Windows default colorspace)
};
enum class BitmapIntent: uint32_t {
ABS_COLORIMETRIC, // Maintains white point; match to nearest color in dest gamut.
BUSINESS, // Maintains saturation; used when undithered colors are required.
GRAPHICS, // Maintains colorimetric match; used for everything
IMAGES // Maintains contrast; used for photographs and natural images
};
struct FP2d30 {
int32_t fraction: 30;
int32_t whole: 2;
FP2d30(): fraction(0), whole(0) {}
};
struct FP2d30Vec {
FP2d30 x, y, z;
};
struct BMPFileHeader {
const int8_t magic_b = 'B';
const int8_t magic_m = 'M';
uint32_t size = 0;
const int16_t reserved1 = 0;
const int16_t reserved2 = 0;
const uint32_t dataStart;
};
static_assert(sizeof(BMPFileHeader) == 14);
struct BMPInfoHeader {
const uint32_t size = sizeof(*this);
uint32_t width, height;
const uint16_t numMipmaps = 1; // Bitmap still only supports one...
uint16_t bitsPerPixel = 32;
BitmapCompression compression = BitmapCompression::BITFIELDS;
uint32_t imageSize = 0;
uint32_t hPixelsPerMeter = 26;
uint32_t vPixelsPerMeter = 26;
uint32_t colorUsed = 0;
uint32_t colorImportant = 0;
uint32_t maskRed = 0x000000FF;
uint32_t maskGreen = 0x0000FF00;
uint32_t maskBlue = 0x00FF0000;
uint32_t maskAlpha = 0xFF000000;
LogicalColorSpace colorSpace = LogicalColorSpace::sRGB;
FP2d30Vec endpointRed = {};
FP2d30Vec endpointGreen = {};
FP2d30Vec endpointBlue = {};
uint32_t gammaRed = 0;
uint32_t gammaGreen = 0;
uint32_t gammaBlue = 0;
BitmapIntent intent = BitmapIntent::GRAPHICS;
int32_t profileData = 0;
int32_t profileSize = 0;
const int32_t reserved = 0;
bool isRGBA() const {
return maskRed == 0xFF000000 && maskGreen == 0x00FF0000 &&
maskBlue == 0x0000FF00 && maskAlpha == 0x000000FF;
}
bool isARGB() const {
return maskAlpha == 0xFF000000 && maskRed == 0x00FF0000 &&
maskGreen == 0x0000FF00 && maskBlue == 0x000000FF;
}
};
#pragma pack(pop)
} // namespace
unsigned char* image_decode_bmp(
const string &image_data, unsigned int* width, unsigned int* height,
unsigned int* fullwidth, unsigned int* fullheight, bool flipped) {
// Check file size against bitmap header size
if (image_data.length() < sizeof(BMPFileHeader)) {
fprintf(stderr, "Junk bitmap of size %ul", image_data.size());
return nullptr;
}
const BMPFileHeader &bmp_file = *(BMPFileHeader*) image_data.data();
// Verify magic number, check header offset sanity.
if (bmp_file.magic_b != 'B' || bmp_file.magic_m != 'M' ||
bmp_file.dataStart + sizeof(BMPInfoHeader) > image_data.length()) {
fprintf(stderr, "Junk bitmap of size %ul", image_data.size());
return nullptr;
}
const BMPInfoHeader &bmp_info =
*(BMPInfoHeader*) (image_data.data() + sizeof(BMPFileHeader));
if(bmp_info.bitsPerPixel != 32 && bmp_info.bitsPerPixel != 24) {
fprintf(stderr, "No support for %dbpp bitmaps\n", bmp_info.bitsPerPixel);
return nullptr;
}
const bool rgba = bmp_info.isRGBA();
const bool argb = bmp_info.isARGB();
if (bmp_info.bitsPerPixel == 32 && !rgba && !argb) {
fprintf(stderr, "No support for mask format (%08X, %08X, %08X, %08X)\n",
bmp_info.maskRed, bmp_info.maskGreen, bmp_info.maskBlue,
bmp_info.maskAlpha);
return nullptr;
}
const unsigned widfull = nlpo2dc(bmp_info.width) + 1;
const unsigned hgtfull = nlpo2dc(bmp_info.height) + 1;
const unsigned bitmap_size = widfull*hgtfull*4;
unsigned char* bitmap = new unsigned char[bitmap_size](); // Initialize to 0.
// Calculate the number of nulls that follows each line.
const int overlap = bmp_info.width * (bmp_info.bitsPerPixel / 8) % 4;
const int pad = overlap ? 4 - overlap : 0;
printf("Bitmap pad: %d\n", pad);
const char *bmp_it = image_data.data() + bmp_file.dataStart;
for (unsigned ih = 0; ih < bmp_info.height; ih++) {
unsigned tmp = 0;
if (flipped) {
tmp = ih * widfull * 4;
} else {
tmp = (bmp_info.height - 1 - ih) * widfull * 4;
}
for (unsigned iw = 0; iw < bmp_info.width; iw++) {
if (bmp_info.bitsPerPixel == 24) {
bitmap[tmp+0] = *bmp_it++;
bitmap[tmp+1] = *bmp_it++;
bitmap[tmp+2] = *bmp_it++;
bitmap[tmp+3] = (char) 0xFF;
}
else if (bmp_info.bitsPerPixel == 32) {
if (argb) {
bitmap[tmp+0] = *bmp_it++;
bitmap[tmp+1] = *bmp_it++;
bitmap[tmp+2] = *bmp_it++;
bitmap[tmp+3] = *bmp_it++;
} else {
bitmap[tmp+3] = *bmp_it++;
bitmap[tmp+0] = *bmp_it++;
bitmap[tmp+1] = *bmp_it++;
bitmap[tmp+2] = *bmp_it++;
}
}
tmp += 4;
}
bmp_it += pad;
}
*width = bmp_info.width;
*height = bmp_info.height;
*fullwidth = widfull;
*fullheight = hgtfull;
return bitmap;
}
unsigned char* image_load_gif(string filename, unsigned int* width, unsigned int* height, unsigned int* fullwidth, unsigned int* fullheight, int* imgnumb, bool flipped) {
unsigned int error = 0;
unsigned char* image = 0;
error = load_gif_file(filename.c_str(), image, *width, *height, *fullwidth, *fullheight, *imgnumb);
if (error) {
DEBUG_MESSAGE(load_gif_error_text(error), MESSAGE_TYPE::M_ERROR);
return NULL;
}
return image;
}
int image_save_bmp(string filename, const unsigned char* data, unsigned width, unsigned height, unsigned fullwidth, unsigned fullheight, bool flipped) {
unsigned sz = width * height;
FILE *bmp = fopen(filename.c_str(), "wb");
if (!bmp) return -1;
fwrite("BM", 2, 1, bmp);
sz <<= 2;
fwrite(&sz,4,1,bmp);
fwrite("\0\0\0\0\x36\0\0\0\x28\0\0",12,1,bmp);
fwrite(&width,4,1,bmp);
fwrite(&height,4,1,bmp);
//NOTE: x20 = 32bit full color, x18 = 24bit no alpha
fwrite("\1\0\x20\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",28,1,bmp);
unsigned bytes = 4;
width *= bytes;
fullwidth *= bytes;
unsigned lastbyte = fullwidth * height;
for (unsigned i = 0; i < lastbyte; i += fullwidth) {
unsigned tmp = i;
if (!flipped) {
tmp = lastbyte - i;
}
for (unsigned ii = 0; ii < width; ii += bytes) {
fwrite(&data[tmp + ii + 0],sizeof(char),1,bmp);
fwrite(&data[tmp + ii + 1],sizeof(char),1,bmp);
fwrite(&data[tmp + ii + 2],sizeof(char),1,bmp);
fwrite(&data[tmp + ii + 3],sizeof(char),1,bmp);
}
}
fclose(bmp);
return 0;
}
}