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