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