1 /////////////////////////////////////////
2 //
3 //             OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10 
11 
12 // Worm skin class
13 // Created 16/6/08
14 // Karel Petranek
15 
16 #include <typeinfo>
17 
18 #include "CGameSkin.h"
19 #include "DeprecatedGUI/Graphics.h"
20 #include "StringUtils.h"
21 #include "MathLib.h"
22 #include "LieroX.h" // for bDedicated
23 #include "Debug.h"
24 #include "Mutex.h"
25 #include "Condition.h"
26 #include "CMap.h" // for CMap::DrawObjectShadow
27 #include "PixelFunctors.h"
28 
29 // global mutex to force only one execution at time
30 static Mutex skinActionHandlerMutex;
31 
32 struct Skin_Action : Action {
33 	bool breakSignal;
34 	CGameSkin* skin;
Skin_ActionSkin_Action35 	Skin_Action(CGameSkin* s) : breakSignal(false), skin(s) {}
36 };
37 
38 struct GameSkinPreviewDrawer : DynDrawIntf {
39 	Mutex mutex;
40 	CGameSkin* skin;
GameSkinPreviewDrawerGameSkinPreviewDrawer41 	GameSkinPreviewDrawer(CGameSkin* s) : DynDrawIntf(s->iSkinWidth,s->iSkinHeight), skin(s) {}
42 	void draw(SDL_Surface* surf, int x, int y);
43 };
44 
45 struct CGameSkin::Thread {
46 	Mutex mutex;
47 	Condition signal;
48 	bool ready;
49 	typedef std::list<Skin_Action*> ActionList;
50 	ActionList actionQueue;
51 	Skin_Action* curAction;
52 
53 	GameSkinPreviewDrawer* skinPreviewDrawerP;
54 	SmartPointer<DynDrawIntf> skinPreviewDrawer;
55 
ThreadCGameSkin::Thread56 	Thread(CGameSkin* s) : ready(true), curAction(NULL), skinPreviewDrawerP(NULL) {
57 		skinPreviewDrawer = skinPreviewDrawerP = new GameSkinPreviewDrawer(s);
58 	}
~ThreadCGameSkin::Thread59 	~Thread() {
60 		forceStopThread();
61 		if(actionQueue.size() > 0) {
62 			// Note: We should normally not get here. If we would, it means that we add some actions
63 			// at some point in the code but we don't assure that the thread is running after.
64 			// The thread itself always assures that at the exit of the thread, no further action is in the queue.
65 			warnings << "CGameSkin::Thread uninit: still actions in queue" << endl;
66 			removeActions__unsafe();
67 		}
68 		Mutex::ScopedLock lock(skinPreviewDrawerP->mutex);
69 		skinPreviewDrawerP->skin = NULL;
70 	}
71 
pushAction__unsafeCGameSkin::Thread72 	void pushAction__unsafe(Skin_Action* a) {
73 		actionQueue.push_back(a);
74 	}
75 
cleanAction__unsafeCGameSkin::Thread76 	static void cleanAction__unsafe(Skin_Action*& a) {
77 		delete a;
78 		a = NULL;
79 	}
80 
removeActions__unsafeCGameSkin::Thread81 	void removeActions__unsafe(const std::type_info& actionType) {
82 		for(ActionList::iterator i = actionQueue.begin(); i != actionQueue.end(); ) {
83 			if(typeid(**i) == actionType) {
84 				cleanAction__unsafe(*i);
85 				i = actionQueue.erase(i);
86 			} else
87 				++i;
88 		}
89 	}
90 
removeActions__unsafeCGameSkin::Thread91 	void removeActions__unsafe() {
92 		for(ActionList::iterator i = actionQueue.begin(); i != actionQueue.end(); ++i)
93 			cleanAction__unsafe(*i);
94 		actionQueue.clear();
95 	}
96 
97 	// run this after you added something to actionQueue to be sure that it will get handled
startThread__unsafeCGameSkin::Thread98 	void startThread__unsafe(CGameSkin* skin) {
99 		if(!ready) return; // !ready -> thread already running
100 		struct SkinActionHandler : Action {
101 			CGameSkin* skin;
102 			SkinActionHandler(CGameSkin* s) : skin(s) {}
103 			int handle() {
104 				int lastRet = 0;
105 				Mutex::ScopedLock lock(skin->thread->mutex);
106 				while(skin->thread->actionQueue.size() > 0) {
107 					skin->thread->curAction = skin->thread->actionQueue.front();
108 					skin->thread->actionQueue.pop_front();
109 
110 					skin->thread->mutex.unlock();
111 					skinActionHandlerMutex.lock(); // just to force only one action at a time globally
112 					lastRet = skin->thread->curAction->handle();
113 					skinActionHandlerMutex.unlock();
114 					skin->thread->mutex.lock();
115 					CGameSkin::Thread::cleanAction__unsafe(skin->thread->curAction);
116 				}
117 				skin->thread->ready = true;
118 				skin->thread->signal.broadcast();
119 				return lastRet;
120 			}
121 		};
122 		threadPool->start(new SkinActionHandler(skin), "CGameSkin handler", true);
123 		ready = false;
124 	}
125 
startThreadCGameSkin::Thread126 	void startThread(CGameSkin* skin) {
127 		Mutex::ScopedLock lock(mutex);
128 		startThread__unsafe(skin);
129 	}
130 
forceStopThreadCGameSkin::Thread131 	void forceStopThread() {
132 		Mutex::ScopedLock lock(mutex);
133 		while(!ready) {
134 			removeActions__unsafe();
135 			if(curAction) curAction->breakSignal = true;
136 			signal.wait(mutex);
137 		}
138 	}
139 };
140 
init(int fw,int fh,int fs,int sw,int sh)141 void CGameSkin::init(int fw, int fh, int fs, int sw, int sh) {
142 	bmpSurface = NULL;
143 	bmpMirrored = NULL;
144 	bmpShadow = NULL;
145 	bmpMirroredShadow = NULL;
146 	bmpPreview = NULL;
147 	bmpNormal = NULL;
148 	sFileName = "";
149 	iDefaultColor = iColor = Color(128, 128, 128);
150 	bColorized = false;
151 	iBotIcon = -1;
152 
153 	iFrameWidth = fw;
154 	iFrameHeight = fh;
155 	iFrameSpacing = fs;
156 	iSkinWidth = sw;
157 	iSkinHeight = sh;
158 
159 	thread = new Thread(this);
160 }
161 
uninit()162 void CGameSkin::uninit() {
163 	if(thread) {
164 		delete thread;
165 		thread = NULL;
166 	}
167 	// all other stuff have its own destructors
168 }
169 
CGameSkin(const std::string & file,int fw,int fh,int fs,int sw,int sh)170 CGameSkin::CGameSkin(const std::string &file, int fw, int fh, int fs, int sw, int sh) : thread(NULL)
171 {
172 	init(fw,fh,fs,sw,sh);
173 
174 	Change(file);
175 }
176 
CGameSkin(int fw,int fh,int fs,int sw,int sh)177 CGameSkin::CGameSkin(int fw, int fh, int fs, int sw, int sh) : thread(NULL)
178 {
179 	init(fw,fh,fs,sw,sh);
180 }
181 
CGameSkin(const CGameSkin & skin)182 CGameSkin::CGameSkin(const CGameSkin& skin) : thread(NULL)
183 {
184 	// we will init the thread also there
185 	operator=(skin);
186 }
187 
~CGameSkin()188 CGameSkin::~CGameSkin()
189 {
190 	uninit();
191 }
192 
193 
194 struct SkinAction_Load : Skin_Action {
195 	bool genPreview;
SkinAction_LoadSkinAction_Load196 	SkinAction_Load(CGameSkin* s, bool p) : Skin_Action(s), genPreview(p) {}
handleSkinAction_Load197 	int handle() {
198 		skin->Load_Execute(breakSignal);
199 		if(breakSignal) return 0;
200 		if(genPreview) skin->GeneratePreview();
201 		return 0;
202 	}
203 };
204 
205 
Load_Execute(bool & breakSignal)206 void CGameSkin::Load_Execute(bool& breakSignal) {
207 	bmpSurface = LoadGameImage("skins/" + sFileName, true);
208 	if(breakSignal) return;
209 
210 	if (!bmpSurface.get()) { // Try to load the default skin if the given one failed
211 		warnings << "CGameSkin::Change: couldn't find skin " << sFileName << endl;
212 		bmpSurface = LoadGameImage("skins/default.png", true);
213 	}
214 
215 	if (bmpSurface.get())  {
216 		SetColorKey(bmpSurface.get());
217 		if (bmpSurface->w % iFrameWidth != 0 || bmpSurface->h != 2 * iFrameHeight) {
218 			notes << "The skin " << sFileName << " has a non-standard size (" << bmpSurface->w << "x" << bmpSurface->h << ")" << endl;
219 			SmartPointer<SDL_Surface> old = bmpSurface;
220 			bmpSurface = gfxCreateSurfaceAlpha( old->w - (old->w % iFrameWidth), 2 * iFrameHeight );
221 			if(bmpSurface.get()) {
222 				CopySurface(bmpSurface.get(), old.get(), 0, 0, 0, 0, bmpSurface->w, MIN(old->h, 2 * iFrameHeight));
223 				SetColorKey(bmpSurface.get());
224 			}
225 			else
226 				warnings << "CGameSkin: Cannot create fixed surface" << endl;
227 		}
228 	}
229 
230 	if (bmpSurface.get())  {
231 		if(getFrameCount() < 5) {
232 			// GeneratePreview would crash in this case
233 			warnings << "CGameSkin: skin " << sFileName << " too small: only " << getFrameCount() << " frames" << endl;
234 			bmpSurface = NULL;
235 		}
236 	}
237 
238 	if(breakSignal) return;
239 	GenerateNormalSurface();
240 	if(breakSignal) return;
241 	GenerateShadow();
242 	if(breakSignal) return;
243 	GenerateMirroredImage();
244 }
245 
246 ////////////////////
247 // Change the skin
Change(const std::string & file)248 void CGameSkin::Change(const std::string &file)
249 {
250 	if(stringcaseequal(sFileName, file))
251 		return;
252 
253 	thread->forceStopThread(); // also removes all actions
254 	// We are assuming here that no other thread is accessing the skin right now in this moment.
255 
256 	sFileName = file;
257 	if(bDedicated) return;
258 
259 	thread->pushAction__unsafe(new SkinAction_Load(this, /* generatePreview = */ !bColorized));
260 
261 	if (bColorized) {
262 		bColorized = false; // To force the recolorization
263 		Colorize(iColor);
264 	}
265 
266 	thread->startThread(this);
267 }
268 
269 /////////////////////
270 // Prepares the non-mirrored surface
PrepareNormalSurface()271 bool CGameSkin::PrepareNormalSurface()
272 {
273 	// Check
274 	if (!bmpSurface.get() || bDedicated)
275 		return false;
276 
277 	// Allocate
278 	if (!bmpNormal.get())  {
279 		bmpNormal = gfxCreateSurfaceAlpha(bmpSurface->w, iFrameHeight);
280 		if (!bmpNormal.get())
281 			return false;
282 	}
283 
284 	FillSurfaceTransparent(bmpNormal.get());
285 
286 	return true;
287 }
288 
289 ///////////////////////
290 // Generates the normal surface, no colorization is done
GenerateNormalSurface()291 void CGameSkin::GenerateNormalSurface()
292 {
293 	if (!PrepareNormalSurface())
294 		return;
295 
296 	// Just copy the upper row
297 	CopySurface(bmpNormal.get(), bmpSurface.get(), 0, 0, 0, 0, bmpNormal->w, bmpNormal->h);
298 }
299 
300 //////////////////
301 // Prepare the mirrored surface
PrepareMirrorSurface()302 bool CGameSkin::PrepareMirrorSurface()
303 {
304 	// Check
305 	if (!bmpSurface.get() || bDedicated)
306 		return false;
307 
308 	// Allocate
309 	if (!bmpMirrored.get())  {
310 		bmpMirrored = gfxCreateSurfaceAlpha(bmpSurface->w, iFrameHeight);
311 		if (!bmpMirrored.get())
312 			return false;
313 	}
314 
315 	FillSurfaceTransparent(bmpMirrored.get());
316 
317 	return true;
318 }
319 
320 ////////////////////
321 // Generate a mirrored image
GenerateMirroredImage()322 void CGameSkin::GenerateMirroredImage()
323 {
324 	if (!PrepareMirrorSurface())
325 		return;
326 
327 	DrawImageAdv_Mirror(bmpMirrored.get(), bmpSurface.get(), 0, 0, 0, 0, bmpMirrored->w, bmpMirrored->h);
328 }
329 
330 //////////////////
331 // Prepares the preview surface, returns false when there was some error
PreparePreviewSurface()332 bool CGameSkin::PreparePreviewSurface()
333 {
334 	// No surfaces in dedicated mode
335 	if (bDedicated)
336 		return false;
337 
338 	// Allocate
339 	if (!bmpPreview.get())  {
340 		bmpPreview = gfxCreateSurfaceAlpha(iSkinWidth, iSkinHeight);
341 		if (!bmpPreview.get())
342 			return false;
343 	}
344 
345 	// Fill with pink
346 	FillSurfaceTransparent(bmpPreview.get());
347 
348 	return bmpNormal.get() != NULL;
349 }
350 
351 /////////////////////
352 // Generates a preview image
GeneratePreview()353 void CGameSkin::GeneratePreview()
354 {
355 	if (!PreparePreviewSurface())
356 		return;
357 
358 	// Worm image
359 	static const int preview_frame = 4;
360 	int sx = preview_frame * iFrameWidth + iFrameSpacing;
361 	CopySurface(bmpPreview.get(), bmpNormal.get(), sx, 0, 0, 0, bmpPreview->w, bmpPreview->h);
362 
363 	// CPU image
364 	if (iBotIcon >= 0 && DeprecatedGUI::gfxGame.bmpAI.get())
365 		DrawImageAdv(bmpPreview.get(), DeprecatedGUI::gfxGame.bmpAI.get(),
366 			iBotIcon * CPU_WIDTH, 0, 0, iSkinHeight - DeprecatedGUI::gfxGame.bmpAI->h, CPU_WIDTH, DeprecatedGUI::gfxGame.bmpAI->h);
367 }
368 
369 //////////////////
370 // Prepares the shadow surface, returns false when there was some error
PrepareShadowSurface()371 bool CGameSkin::PrepareShadowSurface()
372 {
373 	// Make sure we have something to create the shadow from
374 	if (!bmpSurface.get() || bDedicated)
375 		return false;
376 	if (bmpSurface->h < iFrameHeight)
377 		return false;
378 
379 	// Allocate the shadow surface
380 	if (!bmpShadow.get())  {
381 		bmpShadow = gfxCreateSurface(bmpSurface.get()->w, iFrameHeight);
382 		if (!bmpShadow.get())
383 			return false;
384 	}
385 
386 	// Allocate the shadow mirror image
387 	if (!bmpMirroredShadow.get())  {
388 		bmpMirroredShadow = gfxCreateSurface(bmpSurface->w, iFrameHeight);
389 		if (!bmpMirroredShadow.get())
390 			return false;
391 	}
392 
393 	// Set the color key & alpha
394 	SetColorKey(bmpShadow.get());
395 	SetColorKey(bmpMirroredShadow.get());
396 	SetPerSurfaceAlpha(bmpShadow.get(), SHADOW_OPACITY);
397 	SetPerSurfaceAlpha(bmpMirroredShadow.get(), SHADOW_OPACITY);
398 
399 	// Clear the shadow surface
400 	FillSurfaceTransparent(bmpShadow.get());
401 	FillSurfaceTransparent(bmpMirroredShadow.get());
402 
403 	return true;
404 }
405 
406 //////////////////
407 // Generate a shadow surface for the skin
GenerateShadow()408 void CGameSkin::GenerateShadow()
409 {
410 	if (!PrepareShadowSurface())
411 		return;
412 
413 	// Lock the surface & get the pixels
414 	LOCK_OR_QUIT(bmpSurface);
415 	LOCK_OR_QUIT(bmpShadow);
416 	LOCK_OR_QUIT(bmpMirroredShadow);
417 	Uint8 *srcrow = (Uint8 *)bmpSurface.get()->pixels;
418 	Uint8 *srcpix = NULL;
419 	int srcbpp = bmpSurface->format->BytesPerPixel;
420 
421 	const Uint32 black = SDL_MapRGB(bmpShadow->format, 0, 0, 0);
422 	int width = MIN(MIN(bmpSurface->w, bmpShadow->w), bmpMirroredShadow->w);
423 
424 	// Go through the pixels and put the non-transparent ones to the shadow surface
425 	for (int y = 0; y < iFrameHeight; ++y)  {
426 		srcpix = srcrow;
427 
428 		for (int x = 0; x < width; ++x)  {
429 			if (!IsTransparent(bmpSurface.get(), GetPixelFromAddr(srcpix, srcbpp)))  {
430 				PutPixel(bmpShadow.get(), x, y, black);
431 				PutPixel(bmpMirroredShadow.get(), bmpMirroredShadow->w - x - 1, y, black);
432 			}
433 			srcpix += srcbpp;
434 		}
435 
436 		srcrow += bmpSurface->pitch;
437 	}
438 
439 	// Unlock
440 	UnlockSurface(bmpSurface);
441 	UnlockSurface(bmpShadow);
442 	UnlockSurface(bmpMirroredShadow);
443 }
444 
445 
446 ///////////////////
447 // Assignment operator
operator =(const CGameSkin & oth)448 CGameSkin& CGameSkin::operator =(const CGameSkin &oth)
449 {
450 	if (this != &oth)  { // Check for self-assignment
451 		// we must do this because we could need surfaces of different width
452 		uninit();
453 		init(oth.iFrameWidth, oth.iFrameHeight, oth.iFrameSpacing, oth.iSkinWidth, oth.iSkinHeight);
454 		iBotIcon = oth.iBotIcon;
455 
456 		// we must reload it because it's not guaranteed that the other skin itself is ready
457 		iDefaultColor = oth.iDefaultColor;
458 		bColorized = oth.bColorized;
459 		iColor = oth.iColor;
460 		sFileName = "";
461 		Change(oth.sFileName);
462 	}
463 	return *this;
464 }
465 
466 ////////////////////
467 // Comparison operator
operator ==(const CGameSkin & oth)468 bool CGameSkin::operator ==(const CGameSkin &oth)
469 {
470 	if (sFileName.size())
471 		return stringcaseequal(sFileName, oth.sFileName);
472 	else
473 		return bmpSurface.get() == oth.bmpSurface.get();
474 }
475 
476 ///////////////////////
477 // Draw the worm skin at the specified coordinates
Draw(SDL_Surface * surf,int x,int y,int frame,bool draw_cpu,bool mirrored,bool blockUntilReady)478 void CGameSkin::Draw(SDL_Surface *surf, int x, int y, int frame, bool draw_cpu, bool mirrored, bool blockUntilReady)
479 {
480 	// No skins in dedicated mode
481 	if (bDedicated)
482 		return;
483 
484 	Mutex::ScopedLock lock(thread->mutex);
485 	if(!thread->ready) {
486 		if(!blockUntilReady) {
487 			DrawLoadingAni(surf, x + iSkinWidth/2, y + iSkinWidth/2, iSkinWidth/2, iSkinHeight/2, Color(255,0,0), Color(0,255,0), LAT_CAKE);
488 			return;
489 		}
490 		while(!thread->ready) thread->signal.wait(thread->mutex);
491 	}
492 
493 	if(bmpMirrored.get() == NULL || bmpNormal.get() == NULL) return;
494 
495 	if (getFrameCount() != 0)
496 		frame %= getFrameCount();
497 
498 	// Get the correct frame
499 	const int sx = frame * iFrameWidth + iFrameSpacing;
500 	const int sy = (iFrameHeight - iSkinHeight);
501 
502 	// Draw the skin
503 	if (mirrored)  {
504 		DrawImageAdv(surf, bmpMirrored.get(), bmpMirrored->w - sx - iSkinWidth - 1, sy, x, y, iSkinWidth, iSkinHeight);
505 	} else {
506 		DrawImageAdv(surf, bmpNormal.get(), sx, sy, x, y, iSkinWidth, iSkinHeight);
507 	}
508 
509 	// Bot icon?
510 	if (iBotIcon >= 0 && draw_cpu && DeprecatedGUI::gfxGame.bmpAI.get())  {
511 		DrawImageAdv(surf, DeprecatedGUI::gfxGame.bmpAI.get(),
512 		iBotIcon * CPU_WIDTH, 0, 0, iSkinHeight - DeprecatedGUI::gfxGame.bmpAI->h, CPU_WIDTH, DeprecatedGUI::gfxGame.bmpAI->h);
513 	}
514 }
515 
draw(SDL_Surface * dest,int x,int y)516 void GameSkinPreviewDrawer::draw(SDL_Surface* dest, int x, int y) {
517 	Mutex::ScopedLock lock(mutex);
518 	if(skin) {
519 		Mutex::ScopedLock lock2(skin->thread->mutex);
520 		if(skin->thread->ready && skin->bmpPreview.get())
521 			DrawImage(dest, skin->bmpPreview, x, y);
522 		else
523 			DrawLoadingAni(dest, x + w/2, y + h/2, w/2, h/2, Color(255,0,0), Color(0,255,0), LAT_CAKE);
524 	}
525 	else DrawCross(dest, x, y, WORM_SKIN_WIDTH, WORM_SKIN_HEIGHT, Color(255,0,0));
526 }
527 
getPreview()528 SmartPointer<DynDrawIntf> CGameSkin::getPreview() {
529 	return thread->skinPreviewDrawer;
530 }
531 
532 /////////////////////
533 // Draw the worm skin shadow
DrawShadow(SDL_Surface * surf,int x,int y,int frame,bool mirrored)534 void CGameSkin::DrawShadow(SDL_Surface *surf, int x, int y, int frame, bool mirrored)
535 {
536 	// No skins in dedicated mode
537 	if (bDedicated) return;
538 
539 	Mutex::ScopedLock lock(thread->mutex);
540 	if(!thread->ready) return;
541 
542 	if(bmpMirrored.get() == NULL || bmpNormal.get() == NULL) return;
543 
544 	if (getFrameCount() != 0)
545 		frame %= getFrameCount();
546 
547 	// Get the correct frame
548 	const int sx = frame * iFrameWidth + iFrameSpacing;
549 	const int sy = (iFrameHeight - iSkinHeight);
550 
551 	// Draw the shadow
552 	if (mirrored)  {
553 		DrawImageAdv(surf, bmpMirroredShadow.get(), bmpMirroredShadow->w - sx - iSkinWidth - 1, sy, x, y, iSkinWidth, iSkinHeight);
554 	} else {
555 		DrawImageAdv(surf, bmpShadow.get(), sx, sy, x, y, iSkinWidth, iSkinHeight);
556 	}
557 }
558 
DrawShadowOnMap(CMap * cMap,CViewport * v,SDL_Surface * surf,int x,int y,int frame,bool mirrored)559 void CGameSkin::DrawShadowOnMap(CMap* cMap, CViewport* v, SDL_Surface *surf, int x, int y, int frame, bool mirrored) {
560 	// No skins in dedicated mode
561 	if (bDedicated) return;
562 
563 	Mutex::ScopedLock lock(thread->mutex);
564 	if(!thread->ready) return;
565 
566 	if(bmpMirrored.get() == NULL || bmpNormal.get() == NULL) return;
567 
568 	if (getFrameCount() != 0)
569 		frame %= getFrameCount();
570 
571 	// Get the correct frame
572 	const int sx = frame * iFrameWidth + iFrameSpacing;
573 	const int sy = (iFrameHeight - iSkinHeight);
574 
575 	static const int drop = 4;
576 
577 	// draw the shadow
578 	if (mirrored)  {
579 		cMap->DrawObjectShadow(surf, bmpMirroredShadow.get(), bmpMirroredShadow.get(), bmpMirroredShadow->w - sx - iSkinWidth - 1, sy, iSkinWidth, iSkinHeight, v, x - iSkinWidth/2 + drop, y - iSkinHeight/2 + drop);
580 	} else {
581 		cMap->DrawObjectShadow(surf, bmpShadow.get(), bmpShadow.get(), sx, sy, iSkinWidth, iSkinHeight, v, x - iSkinWidth/2 + drop, y - iSkinHeight/2 + drop);
582 	}
583 }
584 
585 struct SkinAction_Colorize : Skin_Action {
SkinAction_ColorizeSkinAction_Colorize586 	SkinAction_Colorize(CGameSkin* s) : Skin_Action(s) {}
handleSkinAction_Colorize587 	int handle() {
588 		skin->Colorize_Execute(breakSignal);
589 		return 0;
590 	}
591 };
592 
Colorize(Color col)593 void CGameSkin::Colorize(Color col) {
594 	// No skins in dedicated mode
595 	if (bDedicated) return;
596 
597 	Mutex::ScopedLock lock(thread->mutex);
598 
599 	// Check if we need to change the color
600 	if (bColorized && col == iColor)
601 		return;
602 
603 	iColor = col;
604 	bColorized = true;
605 
606 	thread->removeActions__unsafe(typeid(SkinAction_Colorize));
607 	thread->pushAction__unsafe(new SkinAction_Colorize(this));
608 	thread->startThread__unsafe(this);
609 }
610 
611 ////////////////////////
612 // Colorize the skin
Colorize_Execute(bool & breakSignal)613 void CGameSkin::Colorize_Execute(bool& breakSignal)
614 {
615 	if (!bmpSurface.get() || !bmpNormal.get() || !bmpMirrored.get())
616 		return;
617 	if (bmpSurface->h < 2 * iFrameHeight)
618 		return;
619 
620 	// Lock
621 	LOCK_OR_QUIT(bmpSurface);
622 	LOCK_OR_QUIT(bmpNormal);
623 	LOCK_OR_QUIT(bmpMirrored);
624 
625 	// Get the color
626 	// TODO: cleanup
627 	thread->mutex.lock();
628 	const Uint8 colR = iColor.r, colG = iColor.g, colB = iColor.b;
629 	thread->mutex.unlock();
630 
631     // Set the colour of the worm
632 	const Uint32 black = SDL_MapRGB(bmpSurface->format, 0, 0, 0);
633 	int width = MIN(MIN(bmpSurface->w, bmpNormal->w), bmpMirrored->w);
634 
635 	// Initialize pixel functions
636 	PixelGet& getter = getPixelGetFunc(bmpSurface.get());
637 	PixelPut& putnormal = getPixelPutFunc(bmpNormal.get());
638 	PixelPut& putmirr = getPixelPutFunc(bmpMirrored.get());
639 	Uint8 *surfrow = GetPixelAddr(bmpSurface.get(), 0, 0);
640 	Uint8 *normalrow = GetPixelAddr(bmpNormal.get(), 0, 0);
641 	Uint8 *mirrrow = GetPixelAddr(bmpMirrored.get(), width - 1, 0);
642 
643 	for (int y = 0; y < iFrameHeight; ++y) {
644 
645 		Uint8 *surfpx = surfrow;
646 		Uint8 *normalpx = normalrow;
647 		Uint8 *mirrpx = mirrrow;
648 		for (int x = 0; x < width; ++x,
649 			surfpx += bmpSurface->format->BytesPerPixel,
650 			normalpx += bmpNormal->format->BytesPerPixel,
651 			mirrpx -= bmpMirrored->format->BytesPerPixel) {
652 
653 			// Use the mask to check what colours to ignore
654 			Uint32 pixel = getter.get(surfpx);
655 			Uint32 mask = getter.get(surfpx + iFrameHeight * bmpSurface->pitch);
656 
657             // Black means to just copy the colour but don't alter it
658             if (EqualRGB(mask, black, bmpSurface.get()->format)) {
659 				putnormal.put(normalpx, pixel);
660 				putmirr.put(mirrpx, pixel);
661                 continue;
662             }
663 
664             // Pink means just ignore the pixel completely
665             if (IsTransparent(bmpSurface.get(), mask))
666                 continue;
667 
668 			Color colorized(bmpSurface->format, pixel);
669 
670             // Must be white (or some over unknown colour)
671 			colorized.r = MIN(255, (colorized.r * colR) / 96);
672 			colorized.g = MIN(255, (colorized.g * colG) / 156);
673 			colorized.b = MIN(255, (colorized.b * colB) / 252);
674 
675 			// Bit of a hack to make sure it isn't completey pink (see through)
676 			if(colorized.r == 255 && colorized.g == 0 && colorized.b == 255) {
677 				colorized.r = 240;
678 				colorized.b = 240;
679 			}
680 
681             // Put the colourised pixel
682 			putnormal.put(normalpx, colorized.get(bmpNormal->format));
683 			putmirr.put(mirrpx, colorized.get(bmpMirrored->format));
684 		}
685 
686 		surfrow += bmpSurface->pitch;
687 		normalrow += bmpSurface->pitch;
688 		mirrrow += bmpMirrored->pitch;
689 
690 		if(breakSignal) break;
691 	}
692 
693 	UnlockSurface(bmpNormal);
694 	UnlockSurface(bmpMirrored);
695 	UnlockSurface(bmpSurface);
696 
697 	if(breakSignal) return;
698 
699 	// Regenerate the preview
700 	GeneratePreview();
701 }
702 
703 ////////////////////
704 // Returns number of frames in the skin animation
getFrameCount() const705 int CGameSkin::getFrameCount() const
706 {
707 	if (bmpSurface.get())
708 		return bmpSurface->w / iFrameWidth;
709 	else
710 		return 0;
711 }
712