1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3 #include <utility>
4 #include <ostream>
5 #include <fstream>
6 #include <string.h>
7 #include <IL/il.h>
8 //#include <IL/ilu.h>
9 #include <SDL_video.h>
10 #include <boost/thread.hpp>
11
12 #ifndef BITMAP_NO_OPENGL
13 #include "Rendering/GL/myGL.h"
14 #include "System/TimeProfiler.h"
15 #endif // !BITMAP_NO_OPENGL
16
17 #include "Bitmap.h"
18 #include "Rendering/GlobalRendering.h"
19 #include "System/bitops.h"
20 #include "System/ScopedFPUSettings.h"
21 #include "System/Log/ILog.h"
22 #include "System/ThreadPool.h"
23 #include "System/FileSystem/DataDirsAccess.h"
24 #include "System/FileSystem/FileQueryFlags.h"
25 #include "System/FileSystem/FileHandler.h"
26
27
28 boost::mutex devilMutex; // devil functions, whilst expensive, aren't thread-save
29
30 static const float blurkernel[9] = {
31 1.0f/16.0f, 2.0f/16.0f, 1.0f/16.0f,
32 2.0f/16.0f, 4.0f/16.0f, 2.0f/16.0f,
33 1.0f/16.0f, 2.0f/16.0f, 1.0f/16.0f
34 };
35 // this is a minimal list of file formats that (should) be available at all platforms
36 static const int formatList[] = {
37 IL_PNG, IL_JPG, IL_TGA, IL_DDS, IL_BMP,
38 IL_RGBA, IL_RGB, IL_BGRA, IL_BGR,
39 IL_COLOUR_INDEX, IL_LUMINANCE, IL_LUMINANCE_ALPHA
40 };
41
IsValidImageFormat(int format)42 static bool IsValidImageFormat(int format) {
43 bool valid = false;
44
45 // check if format is in the allowed list
46 for (size_t i = 0; i < (sizeof(formatList) / sizeof(formatList[0])); i++) {
47 if (format == formatList[i]) {
48 valid = true;
49 break;
50 }
51 }
52
53 return valid;
54 }
55
56
57 //////////////////////////////////////////////////////////////////////
58 // Construction/Destruction
59 //////////////////////////////////////////////////////////////////////
60
61 struct InitializeOpenIL {
InitializeOpenILInitializeOpenIL62 InitializeOpenIL() {
63 ilInit();
64 //iluInit();
65 }
~InitializeOpenILInitializeOpenIL66 ~InitializeOpenIL() {
67 ilShutDown();
68 }
69 } static initOpenIL;
70
71
CBitmap()72 CBitmap::CBitmap()
73 : mem(NULL)
74 , xsize(0)
75 , ysize(0)
76 , channels(4)
77 , compressed(false)
78 #ifndef BITMAP_NO_OPENGL
79 , textype(GL_TEXTURE_2D)
80 , ddsimage(NULL)
81 #endif // !BITMAP_NO_OPENGL
82 {
83 }
84
85
~CBitmap()86 CBitmap::~CBitmap()
87 {
88 delete[] mem;
89 #ifndef BITMAP_NO_OPENGL
90 delete ddsimage;
91 #endif // !BITMAP_NO_OPENGL
92 }
93
94
CBitmap(const CBitmap & old)95 CBitmap::CBitmap(const CBitmap& old)
96 : mem(NULL)
97 , xsize(old.xsize)
98 , ysize(old.ysize)
99 , channels(old.channels)
100 , compressed(false)
101 #ifndef BITMAP_NO_OPENGL
102 , textype(old.textype)
103 , ddsimage(NULL)
104 #endif // !BITMAP_NO_OPENGL
105 {
106 assert(!old.compressed);
107
108 const int size = xsize*ysize * channels;
109 mem = new unsigned char[size];
110 memcpy(mem, old.mem, size);
111 }
112
113
CBitmap(CBitmap && bm)114 CBitmap::CBitmap(CBitmap&& bm)
115 : mem(std::move(bm.mem))
116 , xsize(std::move(bm.xsize))
117 , ysize(std::move(bm.ysize))
118 , channels(std::move(bm.channels))
119 , compressed(std::move(bm.compressed))
120 #ifndef BITMAP_NO_OPENGL
121 , textype(std::move(bm.textype))
122 , ddsimage(std::move(bm.ddsimage))
123 #endif
124 {
125 }
126
127
CBitmap(const unsigned char * data,int _xsize,int _ysize,int _channels)128 CBitmap::CBitmap(const unsigned char* data, int _xsize, int _ysize, int _channels)
129 : mem(NULL)
130 , xsize(_xsize)
131 , ysize(_ysize)
132 , channels(_channels)
133 , compressed(false)
134 #ifndef BITMAP_NO_OPENGL
135 , textype(GL_TEXTURE_2D)
136 , ddsimage(NULL)
137 #endif
138 {
139 const int size = xsize*ysize * channels;
140 mem = new unsigned char[size];
141 memcpy(mem, data, size);
142 }
143
144
operator =(const CBitmap & bm)145 CBitmap& CBitmap::operator=(const CBitmap& bm)
146 {
147 if (this != &bm) {
148 xsize = bm.xsize;
149 ysize = bm.ysize;
150 channels = bm.channels;
151 compressed = bm.compressed;
152
153 const int size = xsize*ysize * channels;
154 delete[] mem;
155 mem = new unsigned char[size];
156 memcpy(mem, bm.mem, size);
157
158 #ifndef BITMAP_NO_OPENGL
159 textype = bm.textype;
160 delete ddsimage;
161 if (bm.ddsimage == NULL) {
162 ddsimage = NULL;
163 } else {
164 ddsimage = new nv_dds::CDDSImage();
165 *ddsimage = *(bm.ddsimage);
166 }
167 #endif // !BITMAP_NO_OPENGL
168 }
169
170 return *this;
171 }
172
173
operator =(CBitmap && bm)174 CBitmap& CBitmap::operator=(CBitmap&& bm)
175 {
176 xsize = bm.xsize;
177 ysize = bm.ysize;
178 channels = bm.channels;
179 compressed = bm.compressed;
180 mem = bm.mem;
181 bm.mem = NULL;
182
183 #ifndef BITMAP_NO_OPENGL
184 textype = bm.textype;
185 ddsimage = bm.ddsimage;
186 bm.ddsimage = NULL;
187 #endif // !BITMAP_NO_OPENGL
188
189 return *this;
190 }
191
192
Alloc(int w,int h,int c)193 void CBitmap::Alloc(int w, int h, int c)
194 {
195 delete[] mem;
196
197 xsize = w;
198 ysize = h;
199 channels = c;
200
201 const int size = w * h * c;
202
203 mem = new unsigned char[size];
204 memset(mem, 0, size);
205 }
206
Alloc(int w,int h)207 void CBitmap::Alloc(int w, int h)
208 {
209 Alloc(w, h, channels);
210 }
211
AllocDummy(const SColor fill)212 void CBitmap::AllocDummy(const SColor fill)
213 {
214 compressed = false;
215 Alloc(1, 1, 4);
216 reinterpret_cast<SColor*>(mem)[0] = fill;
217 }
218
Load(std::string const & filename,unsigned char defaultAlpha)219 bool CBitmap::Load(std::string const& filename, unsigned char defaultAlpha)
220 {
221 #ifndef BITMAP_NO_OPENGL
222 ScopedTimer timer("Textures::CBitmap::Load");
223 #endif
224
225 bool noAlpha = true;
226
227 delete[] mem;
228 mem = NULL;
229
230 #ifndef BITMAP_NO_OPENGL
231 textype = GL_TEXTURE_2D;
232 #endif // !BITMAP_NO_OPENGL
233
234 if (filename.find(".dds") != std::string::npos) {
235 #ifndef BITMAP_NO_OPENGL
236 compressed = true;
237 xsize = 0;
238 ysize = 0;
239 channels = 0;
240
241 ddsimage = new nv_dds::CDDSImage();
242 bool status = ddsimage->load(filename);
243
244 if (status) {
245 xsize = ddsimage->get_width();
246 ysize = ddsimage->get_height();
247 channels = ddsimage->get_components();
248 switch (ddsimage->get_type()) {
249 case nv_dds::TextureFlat :
250 textype = GL_TEXTURE_2D;
251 break;
252 case nv_dds::Texture3D :
253 textype = GL_TEXTURE_3D;
254 break;
255 case nv_dds::TextureCubemap :
256 textype = GL_TEXTURE_CUBE_MAP;
257 break;
258 case nv_dds::TextureNone :
259 default :
260 break;
261 }
262 }
263 return status;
264 #else // !BITMAP_NO_OPENGL
265 AllocDummy(); //allocate a dummy texture, as dds aren't supported in headless
266 return true;
267 #endif // !BITMAP_NO_OPENGL
268 }
269
270 compressed = false;
271 channels = 4;
272
273 CFileHandler file(filename);
274 if (file.FileExists() == false) {
275 AllocDummy();
276 return false;
277 }
278
279 unsigned char* buffer = new unsigned char[file.FileSize() + 2];
280 file.Read(buffer, file.FileSize());
281
282 boost::mutex::scoped_lock lck(devilMutex);
283 ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
284 ilEnable(IL_ORIGIN_SET);
285
286 ILuint ImageName = 0;
287 ilGenImages(1, &ImageName);
288 ilBindImage(ImageName);
289
290 {
291 // do not signal floating point exceptions in devil library
292 ScopedDisableFpuExceptions fe;
293
294 const bool success = !!ilLoadL(IL_TYPE_UNKNOWN, buffer, file.FileSize());
295 ilDisable(IL_ORIGIN_SET);
296 delete[] buffer;
297
298 if (success == false) {
299 AllocDummy();
300 return false;
301 }
302 }
303
304 {
305 if (!IsValidImageFormat(ilGetInteger(IL_IMAGE_FORMAT))) {
306 LOG_L(L_ERROR, "Invalid image format for %s: %d", filename.c_str(), ilGetInteger(IL_IMAGE_FORMAT));
307 delete[] buffer;
308 return false;
309 }
310 }
311
312 noAlpha = (ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL) != 4);
313 ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);
314 xsize = ilGetInteger(IL_IMAGE_WIDTH);
315 ysize = ilGetInteger(IL_IMAGE_HEIGHT);
316
317 mem = new unsigned char[xsize * ysize * 4];
318 //ilCopyPixels(0, 0, 0, xsize, ysize, 0, IL_RGBA, IL_UNSIGNED_BYTE, mem);
319 memcpy(mem, ilGetData(), xsize * ysize * 4);
320
321 ilDeleteImages(1, &ImageName);
322
323 if (noAlpha) {
324 for (int y=0; y < ysize; ++y) {
325 for (int x=0; x < xsize; ++x) {
326 mem[((y*xsize+x) * 4) + 3] = defaultAlpha;
327 }
328 }
329 }
330
331 return true;
332 }
333
334
LoadGrayscale(const std::string & filename)335 bool CBitmap::LoadGrayscale(const std::string& filename)
336 {
337 compressed = false;
338 channels = 1;
339
340 CFileHandler file(filename);
341 if (!file.FileExists()) {
342 return false;
343 }
344
345 unsigned char* buffer = new unsigned char[file.FileSize() + 1];
346 file.Read(buffer, file.FileSize());
347
348 boost::mutex::scoped_lock lck(devilMutex);
349 ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
350 ilEnable(IL_ORIGIN_SET);
351
352 ILuint ImageName = 0;
353 ilGenImages(1, &ImageName);
354 ilBindImage(ImageName);
355
356 const bool success = !!ilLoadL(IL_TYPE_UNKNOWN, buffer, file.FileSize());
357 ilDisable(IL_ORIGIN_SET);
358 delete[] buffer;
359
360 if (success == false) {
361 return false;
362 }
363
364 ilConvertImage(IL_LUMINANCE, IL_UNSIGNED_BYTE);
365 xsize = ilGetInteger(IL_IMAGE_WIDTH);
366 ysize = ilGetInteger(IL_IMAGE_HEIGHT);
367
368 delete[] mem;
369 mem = NULL; // to prevent a dead-pointer in case of an out-of-memory exception on the next line
370 mem = new unsigned char[xsize * ysize];
371 memcpy(mem, ilGetData(), xsize * ysize);
372
373 ilDeleteImages(1, &ImageName);
374
375 return true;
376 }
377
378
Save(std::string const & filename,bool opaque) const379 bool CBitmap::Save(std::string const& filename, bool opaque) const
380 {
381 if (compressed) {
382 #ifndef BITMAP_NO_OPENGL
383 return ddsimage->save(filename);
384 #else
385 return false;
386 #endif // !BITMAP_NO_OPENGL
387 }
388
389 unsigned char* buf = new unsigned char[xsize * ysize * 4];
390 const int ymax = (ysize - 1);
391 /* HACK Flip the image so it saves the right way up.
392 (Fiddling with ilOriginFunc didn't do anything?)
393 Duplicated with ReverseYAxis. */
394 for (int y = 0; y < ysize; ++y) {
395 for (int x = 0; x < xsize; ++x) {
396 const int bi = 4 * (x + (xsize * (ymax - y)));
397 const int mi = 4 * (x + (xsize * (y)));
398 buf[bi + 0] = mem[mi + 0];
399 buf[bi + 1] = mem[mi + 1];
400 buf[bi + 2] = mem[mi + 2];
401 buf[bi + 3] = opaque ? 0xff : mem[mi + 3];
402 }
403 }
404
405 boost::mutex::scoped_lock lck(devilMutex);
406 ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
407 ilEnable(IL_ORIGIN_SET);
408
409 ilHint(IL_COMPRESSION_HINT, IL_USE_COMPRESSION);
410 ilSetInteger(IL_JPG_QUALITY, 80);
411
412 ILuint ImageName = 0;
413 ilGenImages(1, &ImageName);
414 ilBindImage(ImageName);
415
416 ilTexImage(xsize, ysize, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, buf);
417
418 const std::string fullpath = dataDirsAccess.LocateFile(filename, FileQueryFlags::WRITE);
419 const bool success = ilSaveImage((char*)fullpath.c_str());
420
421 ilDeleteImages(1, &ImageName);
422 ilDisable(IL_ORIGIN_SET);
423 delete[] buf;
424
425 return success;
426 }
427
428
429 #ifndef BITMAP_NO_OPENGL
CreateTexture(bool mipmaps) const430 const unsigned int CBitmap::CreateTexture(bool mipmaps) const
431 {
432 if (compressed) {
433 return CreateDDSTexture(0, mipmaps);
434 }
435
436 if (mem == NULL) {
437 return 0;
438 }
439
440 // jcnossen: Some drivers return "2.0" as a version string,
441 // but switch to software rendering for non-power-of-two textures.
442 // GL_ARB_texture_non_power_of_two indicates that the hardware will actually support it.
443 if (!globalRendering->supportNPOTs && (xsize != next_power_of_2(xsize) || ysize != next_power_of_2(ysize)))
444 {
445 CBitmap bm = CreateRescaled(next_power_of_2(xsize), next_power_of_2(ysize));
446 return bm.CreateTexture(mipmaps);
447 }
448
449 unsigned int texture;
450
451 glGenTextures(1, &texture);
452 glBindTexture(GL_TEXTURE_2D, texture);
453
454 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
455 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
456 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
457
458 //FIXME add glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for NPOTs
459
460 if (mipmaps) {
461 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
462
463 glBuildMipmaps(GL_TEXTURE_2D, GL_RGBA8, xsize, ysize, GL_RGBA, GL_UNSIGNED_BYTE, mem);
464 } else {
465 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
466 //glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8 ,xsize, ysize, 0,GL_RGBA, GL_UNSIGNED_BYTE, mem);
467 //gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8 ,xsize, ysize, GL_RGBA, GL_UNSIGNED_BYTE, mem);
468
469 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, xsize, ysize, 0, GL_RGBA, GL_UNSIGNED_BYTE, mem);
470 }
471
472 return texture;
473 }
474
475
HandleDDSMipmap(GLenum target,bool mipmaps,int num_mipmaps)476 static void HandleDDSMipmap(GLenum target, bool mipmaps, int num_mipmaps)
477 {
478 if (num_mipmaps > 0) {
479 // dds included the MipMaps use them
480 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
481 } else {
482 if (mipmaps && IS_GL_FUNCTION_AVAILABLE(glGenerateMipmap)) {
483 // create the mipmaps at runtime
484 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
485 glGenerateMipmap(target);
486 } else {
487 // no mipmaps
488 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
489 }
490 }
491 }
492
CreateDDSTexture(unsigned int texID,bool mipmaps) const493 const unsigned int CBitmap::CreateDDSTexture(unsigned int texID, bool mipmaps) const
494 {
495 glPushAttrib(GL_TEXTURE_BIT);
496
497 if (texID == 0) {
498 glGenTextures(1, &texID);
499 }
500
501 switch (ddsimage->get_type())
502 {
503 case nv_dds::TextureNone:
504 glDeleteTextures(1, &texID);
505 texID = 0;
506 break;
507 case nv_dds::TextureFlat: // 1D, 2D, and rectangle textures
508 glEnable(GL_TEXTURE_2D);
509 glBindTexture(GL_TEXTURE_2D, texID);
510 if (!ddsimage->upload_texture2D(0, GL_TEXTURE_2D)) {
511 glDeleteTextures(1, &texID);
512 texID = 0;
513 break;
514 }
515 HandleDDSMipmap(GL_TEXTURE_2D, mipmaps, ddsimage->get_num_mipmaps());
516 break;
517 case nv_dds::Texture3D:
518 glEnable(GL_TEXTURE_3D);
519 glBindTexture(GL_TEXTURE_3D, texID);
520 if (!ddsimage->upload_texture3D()) {
521 glDeleteTextures(1, &texID);
522 texID = 0;
523 break;
524 }
525 HandleDDSMipmap(GL_TEXTURE_3D, mipmaps, ddsimage->get_num_mipmaps());
526 break;
527 case nv_dds::TextureCubemap:
528 glEnable(GL_TEXTURE_CUBE_MAP);
529 glBindTexture(GL_TEXTURE_CUBE_MAP, texID);
530 if (!ddsimage->upload_textureCubemap()) {
531 glDeleteTextures(1, &texID);
532 texID = 0;
533 break;
534 }
535 HandleDDSMipmap(GL_TEXTURE_CUBE_MAP, mipmaps, ddsimage->get_num_mipmaps());
536 break;
537 default:
538 assert(false);
539 break;
540 }
541
542 glPopAttrib();
543 return texID;
544 }
545 #else // !BITMAP_NO_OPENGL
546
CreateTexture(bool mipmaps) const547 const unsigned int CBitmap::CreateTexture(bool mipmaps) const {
548 return 0;
549 }
550
CreateDDSTexture(unsigned int texID,bool mipmaps) const551 const unsigned int CBitmap::CreateDDSTexture(unsigned int texID, bool mipmaps) const {
552 return 0;
553 }
554 #endif // !BITMAP_NO_OPENGL
555
556
CreateAlpha(unsigned char red,unsigned char green,unsigned char blue)557 void CBitmap::CreateAlpha(unsigned char red, unsigned char green, unsigned char blue)
558 {
559 float3 aCol;
560 for(int a=0; a < 3; ++a) {
561 int cCol = 0;
562 int numCounted = 0;
563 for (int y=0; y < ysize; ++y) {
564 for (int x=0; x < xsize; ++x) {
565 const int index = (y*xsize + x) * 4;
566 if ((mem[index + 3] != 0) &&
567 !(
568 (mem[index + 0] == red) &&
569 (mem[index + 1] == green) &&
570 (mem[index + 2] == blue)
571 ))
572 {
573 cCol += mem[index + a];
574 ++numCounted;
575 }
576 }
577 }
578 if (numCounted != 0) {
579 aCol[a] = cCol / 255.0f / numCounted;
580 }
581 }
582
583 SColor c(red, green, blue);
584 SColor a(aCol.x, aCol.y, aCol.z, 0.0f);
585 SetTransparent(c, a);
586 }
587
588
SetTransparent(const SColor & c,const SColor trans)589 void CBitmap::SetTransparent(const SColor& c, const SColor trans)
590 {
591 if (compressed) {
592 return;
593 }
594
595 static const uint32_t RGB = 0x00FFFFFF;
596
597 uint32_t* mem_i = reinterpret_cast<uint32_t*>(mem);
598 for (int y = 0; y < ysize; ++y) {
599 for (int x = 0; x < xsize; ++x) {
600 if ((*mem_i & RGB) == (c.i & RGB))
601 *mem_i = trans.i;
602 mem_i++;
603 }
604 }
605 }
606
607
Renormalize(float3 newCol)608 void CBitmap::Renormalize(float3 newCol)
609 {
610 float3 aCol;
611 float3 colorDif;
612 for (int a=0; a < 3; ++a) {
613 int cCol = 0;
614 int numCounted = 0;
615 for (int y=0; y < ysize; ++y) {
616 for (int x=0; x < xsize;++x) {
617 const unsigned int index = (y*xsize + x) * 4;
618 if (mem[index + 3] != 0) {
619 cCol += mem[index + a];
620 ++numCounted;
621 }
622 }
623 }
624 aCol[a] = cCol / 255.0f / numCounted;
625 cCol /= xsize*ysize;
626 colorDif[a] = newCol[a] - aCol[a];
627 }
628 for (int a=0; a < 3; ++a) {
629 for (int y=0; y < ysize; ++y) {
630 for (int x=0; x < xsize; ++x) {
631 const unsigned int index = (y*xsize + x) * 4;
632 float nc = float(mem[index + a]) / 255.0f + colorDif[a];
633 mem[index + a] = (unsigned char) (std::min(255.f, std::max(0.0f, nc*255)));
634 }
635 }
636 }
637 }
638
639
kernelBlur(CBitmap * dst,const unsigned char * src,int x,int y,int channel,float weight)640 inline static void kernelBlur(CBitmap* dst, const unsigned char* src, int x, int y, int channel, float weight)
641 {
642 float fragment = 0.0f;
643
644 const int pos = (x + y * dst->xsize) * dst->channels + channel;
645
646 for (int i=0; i < 9; ++i) {
647 int yoffset = (i / 3) - 1;
648 int xoffset = (i - (yoffset + 1) * 3) - 1;
649
650 const int tx = x + xoffset;
651 const int ty = y + yoffset;
652 if ((tx < 0) || (tx >= dst->xsize)) {
653 xoffset = 0;
654 }
655 if ((ty < 0) || (ty >= dst->ysize)) {
656 yoffset = 0;
657 }
658 int offset = (yoffset * dst->xsize + xoffset) * dst->channels;
659 if (i == 4) {
660 fragment += weight * blurkernel[i] * src[pos + offset];
661 } else {
662 fragment += blurkernel[i] * src[pos + offset];
663 }
664 }
665
666 dst->mem[pos] = (unsigned char)std::min(255.0f,std::max(0.0f, fragment ));
667 }
668
669
Blur(int iterations,float weight)670 void CBitmap::Blur(int iterations, float weight)
671 {
672 if (compressed) {
673 return;
674 }
675
676 CBitmap* src = this;
677 CBitmap* dst = new CBitmap();
678 dst->channels = src->channels;
679 dst->Alloc(xsize,ysize);
680
681 for (int i=0; i < iterations; ++i) {
682 for_mt(0, ysize, [&](const int y) {
683 for (int x=0; x < xsize; x++) {
684 for (int j=0; j < channels; j++) {
685 kernelBlur(dst, src->mem, x, y, j, weight);
686 }
687 }
688 });
689 std::swap(src, dst);
690 }
691
692 if (dst == this) {
693 // make sure we don't delete `this`
694 std::swap(src, dst);
695 }
696
697 delete dst;
698 }
699
700
CopySubImage(const CBitmap & src,int xpos,int ypos)701 void CBitmap::CopySubImage(const CBitmap& src, int xpos, int ypos)
702 {
703 if (xpos + src.xsize > xsize || ypos + src.ysize > ysize) {
704 LOG_L(L_WARNING, "CBitmap::CopySubImage src image does not fit into dst!");
705 return;
706 }
707
708 if (compressed || src.compressed) {
709 LOG_L(L_WARNING, "CBitmap::CopySubImage can't copy compressed textures!");
710 return;
711 }
712
713 for (int y=0; y < src.ysize; ++y) {
714 const int pixelDst = (((ypos + y) * xsize) + xpos) * channels;
715 const int pixelSrc = ((y * src.xsize) + 0 ) * channels;
716
717 // copy the whole line
718 memcpy(mem + pixelDst, src.mem + pixelSrc, channels * src.xsize);
719 }
720 }
721
722
CanvasResize(const int newx,const int newy,const bool center) const723 CBitmap CBitmap::CanvasResize(const int newx, const int newy, const bool center) const
724 {
725 CBitmap bm;
726
727 if (xsize > newx || ysize > newy) {
728 LOG_L(L_WARNING, "CBitmap::CanvasResize can only upscale (tried to resize %ix%i to %ix%i)!", xsize,ysize,newx,newy);
729 bm.AllocDummy();
730 return bm;
731 }
732
733 const int borderLeft = (center) ? (newx - xsize) / 2 : 0;
734 const int borderTop = (center) ? (newy - ysize) / 2 : 0;
735
736 bm.channels = channels;
737 bm.Alloc(newx, newy);
738 bm.CopySubImage(*this, borderLeft, borderTop);
739
740 return bm;
741 }
742
743
CreateSDLSurface(bool newPixelData) const744 SDL_Surface* CBitmap::CreateSDLSurface(bool newPixelData) const
745 {
746 SDL_Surface* surface = NULL;
747
748 if (channels < 3) {
749 LOG_L(L_WARNING, "CBitmap::CreateSDLSurface works only with 24bit RGB and 32bit RGBA pictures!");
750 return surface;
751 }
752
753 unsigned char* surfData = NULL;
754 if (newPixelData) {
755 // copy pixel data
756 surfData = new unsigned char[xsize * ysize * channels];
757 memcpy(surfData, mem, xsize * ysize * channels);
758 } else {
759 surfData = mem;
760 }
761
762 // This will only work with 24bit RGB and 32bit RGBA pictures
763 surface = SDL_CreateRGBSurfaceFrom(surfData, xsize, ysize, 8 * channels, xsize * channels, 0x000000FF, 0x0000FF00, 0x00FF0000, (channels == 4) ? 0xFF000000 : 0);
764 if ((surface == NULL) && newPixelData) {
765 // cleanup when we failed to the create surface
766 delete[] surfData;
767 }
768
769 return surface;
770 }
771
772
CreateRescaled(int newx,int newy) const773 CBitmap CBitmap::CreateRescaled(int newx, int newy) const
774 {
775 newx = std::max(1, newx);
776 newy = std::max(1, newy);
777
778 CBitmap bm;
779
780 if (compressed) {
781 LOG_L(L_WARNING, "CBitmap::CreateRescaled doesn't work with compressed textures!");
782 bm.AllocDummy();
783 return bm;
784 }
785
786 if (channels != 4) {
787 LOG_L(L_WARNING, "CBitmap::CreateRescaled only works with RGBA data!");
788 bm.AllocDummy();
789 return bm;
790 }
791
792 bm.Alloc(newx, newy);
793
794 const float dx = (float) xsize / newx;
795 const float dy = (float) ysize / newy;
796
797 float cy = 0;
798 for (int y=0; y < newy; ++y) {
799 const int sy = (int) cy;
800 cy += dy;
801 int ey = (int) cy;
802 if (ey == sy) {
803 ey = sy+1;
804 }
805
806 float cx = 0;
807 for (int x=0; x < newx; ++x) {
808 const int sx = (int) cx;
809 cx += dx;
810 int ex = (int) cx;
811 if (ex == sx) {
812 ex = sx + 1;
813 }
814
815 int r=0, g=0, b=0, a=0;
816 for (int y2 = sy; y2 < ey; ++y2) {
817 for (int x2 = sx; x2 < ex; ++x2) {
818 const int index = (y2*xsize + x2) * 4;
819 r += mem[index + 0];
820 g += mem[index + 1];
821 b += mem[index + 2];
822 a += mem[index + 3];
823 }
824 }
825 const int index = (y*bm.xsize + x) * 4;
826 const int denom = (ex - sx) * (ey - sy);
827 bm.mem[index + 0] = r / denom;
828 bm.mem[index + 1] = g / denom;
829 bm.mem[index + 2] = b / denom;
830 bm.mem[index + 3] = a / denom;
831 }
832 }
833
834 return bm;
835 }
836
837
InvertColors()838 void CBitmap::InvertColors()
839 {
840 if (compressed) {
841 return;
842 }
843 for (int y = 0; y < ysize; ++y) {
844 for (int x = 0; x < xsize; ++x) {
845 const int base = ((y * xsize) + x) * 4;
846 mem[base + 0] = 0xFF - mem[base + 0];
847 mem[base + 1] = 0xFF - mem[base + 1];
848 mem[base + 2] = 0xFF - mem[base + 2];
849 // do not invert alpha
850 }
851 }
852 }
853
854
InvertAlpha()855 void CBitmap::InvertAlpha()
856 {
857 if (compressed) return; // Don't try to invert DDS
858 for (int y = 0; y < ysize; ++y) {
859 for (int x = 0; x < xsize; ++x) {
860 const int base = ((y * xsize) + x) * 4;
861 mem[base + 3] = 0xFF - mem[base + 3];
862 }
863 }
864 }
865
866
GrayScale()867 void CBitmap::GrayScale()
868 {
869 if (compressed) {
870 return;
871 }
872 for (int y = 0; y < ysize; ++y) {
873 for (int x = 0; x < xsize; ++x) {
874 const int base = ((y * xsize) + x) * 4;
875 const float illum =
876 (mem[base + 0] * 0.299f) +
877 (mem[base + 1] * 0.587f) +
878 (mem[base + 2] * 0.114f);
879 const unsigned int ival = (unsigned int)(illum * (256.0f / 255.0f));
880 const unsigned char cval = (ival <= 0xFF) ? ival : 0xFF;
881 mem[base + 0] = cval;
882 mem[base + 1] = cval;
883 mem[base + 2] = cval;
884 }
885 }
886 }
887
TintByte(ILubyte value,float tint)888 static ILubyte TintByte(ILubyte value, float tint)
889 {
890 float f = (float)value;
891 f = std::max(0.0f, std::min(255.0f, f * tint));
892 return (unsigned char)f;
893 }
894
895
Tint(const float tint[3])896 void CBitmap::Tint(const float tint[3])
897 {
898 if (compressed) {
899 return;
900 }
901 for (int y = 0; y < ysize; y++) {
902 for (int x = 0; x < xsize; x++) {
903 const int base = ((y * xsize) + x) * 4;
904 mem[base + 0] = TintByte(mem[base + 0], tint[0]);
905 mem[base + 1] = TintByte(mem[base + 1], tint[1]);
906 mem[base + 2] = TintByte(mem[base + 2], tint[2]);
907 // don't touch the alpha channel
908 }
909 }
910 }
911
912
ReverseYAxis()913 void CBitmap::ReverseYAxis()
914 {
915 if (compressed) return; // don't try to flip DDS
916 unsigned char* tmpLine = new unsigned char[channels * xsize];
917 for (int y=0; y < (ysize / 2); ++y) {
918 const int pixelLow = (((y ) * xsize) + 0) * channels;
919 const int pixelHigh = (((ysize - 1 - y) * xsize) + 0) * channels;
920
921 // copy the whole line
922 memcpy(tmpLine, mem + pixelHigh, channels * xsize);
923 memcpy(mem + pixelHigh, mem + pixelLow, channels * xsize);
924 memcpy(mem + pixelLow, tmpLine, channels * xsize);
925 }
926 delete[] tmpLine;
927 }
928