Remove texture atlas / AtlasPointer, rename getTextureRaw to getTexture
[oweals/minetest.git] / src / tile.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "tile.h"
21 #include "debug.h"
22 #include "main.h" // for g_settings
23 #include "filesys.h"
24 #include "settings.h"
25 #include "mesh.h"
26 #include <ICameraSceneNode.h>
27 #include "log.h"
28 #include "gamedef.h"
29 #include "util/string.h"
30 #include "util/container.h"
31 #include "util/thread.h"
32 #include "util/numeric.h"
33
34 /*
35         A cache from texture name to texture path
36 */
37 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
38
39 /*
40         Replaces the filename extension.
41         eg:
42                 std::string image = "a/image.png"
43                 replace_ext(image, "jpg")
44                 -> image = "a/image.jpg"
45         Returns true on success.
46 */
47 static bool replace_ext(std::string &path, const char *ext)
48 {
49         if(ext == NULL)
50                 return false;
51         // Find place of last dot, fail if \ or / found.
52         s32 last_dot_i = -1;
53         for(s32 i=path.size()-1; i>=0; i--)
54         {
55                 if(path[i] == '.')
56                 {
57                         last_dot_i = i;
58                         break;
59                 }
60                 
61                 if(path[i] == '\\' || path[i] == '/')
62                         break;
63         }
64         // If not found, return an empty string
65         if(last_dot_i == -1)
66                 return false;
67         // Else make the new path
68         path = path.substr(0, last_dot_i+1) + ext;
69         return true;
70 }
71
72 /*
73         Find out the full path of an image by trying different filename
74         extensions.
75
76         If failed, return "".
77 */
78 std::string getImagePath(std::string path)
79 {
80         // A NULL-ended list of possible image extensions
81         const char *extensions[] = {
82                 "png", "jpg", "bmp", "tga",
83                 "pcx", "ppm", "psd", "wal", "rgb",
84                 NULL
85         };
86         // If there is no extension, add one
87         if(removeStringEnd(path, extensions) == "")
88                 path = path + ".png";
89         // Check paths until something is found to exist
90         const char **ext = extensions;
91         do{
92                 bool r = replace_ext(path, *ext);
93                 if(r == false)
94                         return "";
95                 if(fs::PathExists(path))
96                         return path;
97         }
98         while((++ext) != NULL);
99         
100         return "";
101 }
102
103 /*
104         Gets the path to a texture by first checking if the texture exists
105         in texture_path and if not, using the data path.
106
107         Checks all supported extensions by replacing the original extension.
108
109         If not found, returns "".
110
111         Utilizes a thread-safe cache.
112 */
113 std::string getTexturePath(const std::string &filename)
114 {
115         std::string fullpath = "";
116         /*
117                 Check from cache
118         */
119         bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
120         if(incache)
121                 return fullpath;
122         
123         /*
124                 Check from texture_path
125         */
126         std::string texture_path = g_settings->get("texture_path");
127         if(texture_path != "")
128         {
129                 std::string testpath = texture_path + DIR_DELIM + filename;
130                 // Check all filename extensions. Returns "" if not found.
131                 fullpath = getImagePath(testpath);
132         }
133         
134         /*
135                 Check from $user/textures/all
136         */
137         if(fullpath == "")
138         {
139                 std::string texture_path = porting::path_user + DIR_DELIM
140                                 + "textures" + DIR_DELIM + "all";
141                 std::string testpath = texture_path + DIR_DELIM + filename;
142                 // Check all filename extensions. Returns "" if not found.
143                 fullpath = getImagePath(testpath);
144         }
145
146         /*
147                 Check from default data directory
148         */
149         if(fullpath == "")
150         {
151                 std::string base_path = porting::path_share + DIR_DELIM + "textures"
152                                 + DIR_DELIM + "base" + DIR_DELIM + "pack";
153                 std::string testpath = base_path + DIR_DELIM + filename;
154                 // Check all filename extensions. Returns "" if not found.
155                 fullpath = getImagePath(testpath);
156         }
157         
158         // Add to cache (also an empty result is cached)
159         g_texturename_to_path_cache.set(filename, fullpath);
160         
161         // Finally return it
162         return fullpath;
163 }
164
165 /*
166         Stores internal information about a texture.
167 */
168
169 struct TextureInfo
170 {
171         std::string name;
172         video::ITexture *texture;
173         video::IImage *img; // The source image
174
175         TextureInfo(
176                         const std::string &name_,
177                         video::ITexture *texture_=NULL,
178                         video::IImage *img_=NULL
179                 ):
180                 name(name_),
181                 texture(texture_),
182                 img(img_)
183         {
184         }
185 };
186
187 /*
188         SourceImageCache: A cache used for storing source images.
189 */
190
191 class SourceImageCache
192 {
193 public:
194         ~SourceImageCache() {
195                 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
196                                 iter != m_images.end(); iter++) {
197                         iter->second->drop();
198                 }
199                 m_images.clear();
200         }
201         void insert(const std::string &name, video::IImage *img,
202                         bool prefer_local, video::IVideoDriver *driver)
203         {
204                 assert(img);
205                 // Remove old image
206                 std::map<std::string, video::IImage*>::iterator n;
207                 n = m_images.find(name);
208                 if(n != m_images.end()){
209                         if(n->second)
210                                 n->second->drop();
211                 }
212
213                 video::IImage* toadd = img;
214                 bool need_to_grab = true;
215
216                 // Try to use local texture instead if asked to
217                 if(prefer_local){
218                         std::string path = getTexturePath(name.c_str());
219                         if(path != ""){
220                                 video::IImage *img2 = driver->createImageFromFile(path.c_str());
221                                 if(img2){
222                                         toadd = img2;
223                                         need_to_grab = false;
224                                 }
225                         }
226                 }
227
228                 if (need_to_grab)
229                         toadd->grab();
230                 m_images[name] = toadd;
231         }
232         video::IImage* get(const std::string &name)
233         {
234                 std::map<std::string, video::IImage*>::iterator n;
235                 n = m_images.find(name);
236                 if(n != m_images.end())
237                         return n->second;
238                 return NULL;
239         }
240         // Primarily fetches from cache, secondarily tries to read from filesystem
241         video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
242         {
243                 std::map<std::string, video::IImage*>::iterator n;
244                 n = m_images.find(name);
245                 if(n != m_images.end()){
246                         n->second->grab(); // Grab for caller
247                         return n->second;
248                 }
249                 video::IVideoDriver* driver = device->getVideoDriver();
250                 std::string path = getTexturePath(name.c_str());
251                 if(path == ""){
252                         infostream<<"SourceImageCache::getOrLoad(): No path found for \""
253                                         <<name<<"\""<<std::endl;
254                         return NULL;
255                 }
256                 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
257                                 <<"\""<<std::endl;
258                 video::IImage *img = driver->createImageFromFile(path.c_str());
259
260                 if(img){
261                         m_images[name] = img;
262                         img->grab(); // Grab for caller
263                 }
264                 return img;
265         }
266 private:
267         std::map<std::string, video::IImage*> m_images;
268 };
269
270 /*
271         TextureSource
272 */
273
274 class TextureSource : public IWritableTextureSource
275 {
276 public:
277         TextureSource(IrrlichtDevice *device);
278         virtual ~TextureSource();
279
280         /*
281                 Example case:
282                 Now, assume a texture with the id 1 exists, and has the name
283                 "stone.png^mineral1".
284                 Then a random thread calls getTextureId for a texture called
285                 "stone.png^mineral1^crack0".
286                 ...Now, WTF should happen? Well:
287                 - getTextureId strips off stuff recursively from the end until
288                   the remaining part is found, or nothing is left when
289                   something is stripped out
290
291                 But it is slow to search for textures by names and modify them
292                 like that?
293                 - ContentFeatures is made to contain ids for the basic plain
294                   textures
295                 - Crack textures can be slow by themselves, but the framework
296                   must be fast.
297
298                 Example case #2:
299                 - Assume a texture with the id 1 exists, and has the name
300                   "stone.png^mineral_coal.png".
301                 - Now getNodeTile() stumbles upon a node which uses
302                   texture id 1, and determines that MATERIAL_FLAG_CRACK
303                   must be applied to the tile
304                 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
305                   has received the current crack level 0 from the client. It
306                   finds out the name of the texture with getTextureName(1),
307                   appends "^crack0" to it and gets a new texture id with
308                   getTextureId("stone.png^mineral_coal.png^crack0").
309
310         */
311         
312         /*
313                 Gets a texture id from cache or
314                 - if main thread, from getTextureIdDirect
315                 - if other thread, adds to request queue and waits for main thread
316         */
317         u32 getTextureId(const std::string &name);
318         
319         /*
320                 Example names:
321                 "stone.png"
322                 "stone.png^crack2"
323                 "stone.png^mineral_coal.png"
324                 "stone.png^mineral_coal.png^crack1"
325
326                 - If texture specified by name is found from cache, return the
327                   cached id.
328                 - Otherwise generate the texture, add to cache and return id.
329                   Recursion is used to find out the largest found part of the
330                   texture and continue based on it.
331
332                 The id 0 points to a NULL texture. It is returned in case of error.
333         */
334         u32 getTextureIdDirect(const std::string &name);
335
336         // Finds out the name of a cached texture.
337         std::string getTextureName(u32 id);
338
339         /*
340                 If texture specified by the name pointed by the id doesn't
341                 exist, create it, then return the cached texture.
342
343                 Can be called from any thread. If called from some other thread
344                 and not found in cache, the call is queued to the main thread
345                 for processing.
346         */
347         video::ITexture* getTexture(u32 id);
348
349         video::ITexture* getTexture(const std::string &name, u32 *id);
350
351         // Returns a pointer to the irrlicht device
352         virtual IrrlichtDevice* getDevice()
353         {
354                 return m_device;
355         }
356
357         bool isKnownSourceImage(const std::string &name)
358         {
359                 bool is_known = false;
360                 bool cache_found = m_source_image_existence.get(name, &is_known);
361                 if(cache_found)
362                         return is_known;
363                 // Not found in cache; find out if a local file exists
364                 is_known = (getTexturePath(name) != "");
365                 m_source_image_existence.set(name, is_known);
366                 return is_known;
367         }
368
369         // Processes queued texture requests from other threads.
370         // Shall be called from the main thread.
371         void processQueue();
372         
373         // Insert an image into the cache without touching the filesystem.
374         // Shall be called from the main thread.
375         void insertSourceImage(const std::string &name, video::IImage *img);
376         
377         // Rebuild images and textures from the current set of source images
378         // Shall be called from the main thread.
379         void rebuildImagesAndTextures();
380         
381 private:
382         
383         // The id of the thread that is allowed to use irrlicht directly
384         threadid_t m_main_thread;
385         // The irrlicht device
386         IrrlichtDevice *m_device;
387         
388         // Cache of source images
389         // This should be only accessed from the main thread
390         SourceImageCache m_sourcecache;
391
392         // Thread-safe cache of what source images are known (true = known)
393         MutexedMap<std::string, bool> m_source_image_existence;
394
395         // A texture id is index in this array.
396         // The first position contains a NULL texture.
397         std::vector<TextureInfo> m_textureinfo_cache;
398         // Maps a texture name to an index in the former.
399         std::map<std::string, u32> m_name_to_id;
400         // The two former containers are behind this mutex
401         JMutex m_textureinfo_cache_mutex;
402         
403         // Queued texture fetches (to be processed by the main thread)
404         RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
405
406         // Textures that have been overwritten with other ones
407         // but can't be deleted because the ITexture* might still be used
408         std::list<video::ITexture*> m_texture_trash;
409 };
410
411 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
412 {
413         return new TextureSource(device);
414 }
415
416 TextureSource::TextureSource(IrrlichtDevice *device):
417                 m_device(device)
418 {
419         assert(m_device);
420         
421         m_textureinfo_cache_mutex.Init();
422         
423         m_main_thread = get_current_thread_id();
424         
425         // Add a NULL TextureInfo as the first index, named ""
426         m_textureinfo_cache.push_back(TextureInfo(""));
427         m_name_to_id[""] = 0;
428 }
429
430 TextureSource::~TextureSource()
431 {
432         video::IVideoDriver* driver = m_device->getVideoDriver();
433
434         unsigned int textures_before = driver->getTextureCount();
435
436         for (std::vector<TextureInfo>::iterator iter =
437                         m_textureinfo_cache.begin();
438                         iter != m_textureinfo_cache.end(); iter++)
439         {
440                 //cleanup texture
441                 if (iter->texture)
442                         driver->removeTexture(iter->texture);
443
444                 //cleanup source image
445                 if (iter->img)
446                         iter->img->drop();
447         }
448         m_textureinfo_cache.clear();
449
450         for (std::list<video::ITexture*>::iterator iter =
451                         m_texture_trash.begin(); iter != m_texture_trash.end();
452                         iter++)
453         {
454                 video::ITexture *t = *iter;
455
456                 //cleanup trashed texture
457                 driver->removeTexture(t);
458         }
459
460         infostream << "~TextureSource() "<< textures_before << "/"
461                         << driver->getTextureCount() << std::endl;
462 }
463
464 u32 TextureSource::getTextureId(const std::string &name)
465 {
466         //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
467
468         {
469                 /*
470                         See if texture already exists
471                 */
472                 JMutexAutoLock lock(m_textureinfo_cache_mutex);
473                 std::map<std::string, u32>::iterator n;
474                 n = m_name_to_id.find(name);
475                 if(n != m_name_to_id.end())
476                 {
477                         return n->second;
478                 }
479         }
480         
481         /*
482                 Get texture
483         */
484         if(get_current_thread_id() == m_main_thread)
485         {
486                 return getTextureIdDirect(name);
487         }
488         else
489         {
490                 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
491
492                 // We're gonna ask the result to be put into here
493                 ResultQueue<std::string, u32, u8, u8> result_queue;
494                 
495                 // Throw a request in
496                 m_get_texture_queue.add(name, 0, 0, &result_queue);
497                 
498                 infostream<<"Waiting for texture from main thread, name=\""
499                                 <<name<<"\""<<std::endl;
500                 
501                 try
502                 {
503                         // Wait result for a second
504                         GetResult<std::string, u32, u8, u8>
505                                         result = result_queue.pop_front(1000);
506                 
507                         // Check that at least something worked OK
508                         assert(result.key == name);
509
510                         return result.item;
511                 }
512                 catch(ItemNotFoundException &e)
513                 {
514                         infostream<<"Waiting for texture timed out."<<std::endl;
515                         return 0;
516                 }
517         }
518         
519         infostream<<"getTextureId(): Failed"<<std::endl;
520
521         return 0;
522 }
523
524 // Overlay image on top of another image (used for cracks)
525 void overlay(video::IImage *image, video::IImage *overlay);
526
527 // Draw an image on top of an another one, using the alpha channel of the
528 // source image
529 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
530                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
531
532 // Brighten image
533 void brighten(video::IImage *image);
534 // Parse a transform name
535 u32 parseImageTransform(const std::string& s);
536 // Apply transform to image dimension
537 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
538 // Apply transform to image data
539 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
540
541 /*
542         Generate image based on a string like "stone.png" or "[crack0".
543         if baseimg is NULL, it is created. Otherwise stuff is made on it.
544 */
545 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
546                 IrrlichtDevice *device, SourceImageCache *sourcecache);
547
548 /*
549         Generates an image from a full string like
550         "stone.png^mineral_coal.png^[crack0".
551 */
552 video::IImage* generate_image_from_scratch(std::string name,
553                 IrrlichtDevice *device, SourceImageCache *sourcecache);
554
555 /*
556         This method generates all the textures
557 */
558 u32 TextureSource::getTextureIdDirect(const std::string &name)
559 {
560         //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
561
562         // Empty name means texture 0
563         if(name == "")
564         {
565                 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
566                 return 0;
567         }
568         
569         /*
570                 Calling only allowed from main thread
571         */
572         if(get_current_thread_id() != m_main_thread)
573         {
574                 errorstream<<"TextureSource::getTextureIdDirect() "
575                                 "called not from main thread"<<std::endl;
576                 return 0;
577         }
578
579         /*
580                 See if texture already exists
581         */
582         {
583                 JMutexAutoLock lock(m_textureinfo_cache_mutex);
584
585                 std::map<std::string, u32>::iterator n;
586                 n = m_name_to_id.find(name);
587                 if(n != m_name_to_id.end())
588                 {
589                         /*infostream<<"getTextureIdDirect(): \""<<name
590                                         <<"\" found in cache"<<std::endl;*/
591                         return n->second;
592                 }
593         }
594
595         /*infostream<<"getTextureIdDirect(): \""<<name
596                         <<"\" NOT found in cache. Creating it."<<std::endl;*/
597         
598         /*
599                 Get the base image
600         */
601
602         char separator = '^';
603
604         /*
605                 This is set to the id of the base image.
606                 If left 0, there is no base image and a completely new image
607                 is made.
608         */
609         u32 base_image_id = 0;
610         
611         // Find last meta separator in name
612         s32 last_separator_position = -1;
613         for(s32 i=name.size()-1; i>=0; i--)
614         {
615                 if(name[i] == separator)
616                 {
617                         last_separator_position = i;
618                         break;
619                 }
620         }
621         /*
622                 If separator was found, construct the base name and make the
623                 base image using a recursive call
624         */
625         std::string base_image_name;
626         if(last_separator_position != -1)
627         {
628                 // Construct base name
629                 base_image_name = name.substr(0, last_separator_position);
630                 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
631                                 " to get base image of \""<<name<<"\" = \""
632                 <<base_image_name<<"\""<<std::endl;*/
633                 base_image_id = getTextureIdDirect(base_image_name);
634         }
635         
636         //infostream<<"base_image_id="<<base_image_id<<std::endl;
637         
638         video::IVideoDriver* driver = m_device->getVideoDriver();
639         assert(driver);
640
641         video::ITexture *t = NULL;
642
643         /*
644                 An image will be built from files and then converted into a texture.
645         */
646         video::IImage *baseimg = NULL;
647         
648         // If a base image was found, copy it to baseimg
649         if(base_image_id != 0)
650         {
651                 JMutexAutoLock lock(m_textureinfo_cache_mutex);
652
653                 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
654                 
655                 if(ti->img == NULL)
656                 {
657                         infostream<<"getTextureIdDirect(): WARNING: NULL image in "
658                                         <<"cache: \""<<base_image_name<<"\""
659                                         <<std::endl;
660                 }
661                 else
662                 {
663                         core::dimension2d<u32> dim = ti->img->getDimension();
664
665                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
666
667                         ti->img->copyTo(
668                                         baseimg, // target
669                                         v2s32(0,0), // position in target
670                                         core::rect<s32>(v2s32(0,0), dim) // from
671                         );
672
673                         /*infostream<<"getTextureIdDirect(): Loaded \""
674                                         <<base_image_name<<"\" from image cache"
675                                         <<std::endl;*/
676                 }
677         }
678         
679         /*
680                 Parse out the last part of the name of the image and act
681                 according to it
682         */
683
684         std::string last_part_of_name = name.substr(last_separator_position+1);
685         //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
686
687         // Generate image according to part of name
688         if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
689         {
690                 errorstream<<"getTextureIdDirect(): "
691                                 "failed to generate \""<<last_part_of_name<<"\""
692                                 <<std::endl;
693         }
694
695         // If no resulting image, print a warning
696         if(baseimg == NULL)
697         {
698                 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
699                                 " create texture \""<<name<<"\""<<std::endl;
700         }
701         
702         if(baseimg != NULL)
703         {
704                 // Create texture from resulting image
705                 t = driver->addTexture(name.c_str(), baseimg);
706         }
707         
708         /*
709                 Add texture to caches (add NULL textures too)
710         */
711
712         JMutexAutoLock lock(m_textureinfo_cache_mutex);
713         
714         u32 id = m_textureinfo_cache.size();
715         TextureInfo ti(name, t, baseimg);
716         m_textureinfo_cache.push_back(ti);
717         m_name_to_id[name] = id;
718
719         /*infostream<<"getTextureIdDirect(): "
720                         <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
721         
722         return id;
723 }
724
725 std::string TextureSource::getTextureName(u32 id)
726 {
727         JMutexAutoLock lock(m_textureinfo_cache_mutex);
728
729         if(id >= m_textureinfo_cache.size())
730         {
731                 errorstream<<"TextureSource::getTextureName(): id="<<id
732                                 <<" >= m_textureinfo_cache.size()="
733                                 <<m_textureinfo_cache.size()<<std::endl;
734                 return "";
735         }
736         
737         return m_textureinfo_cache[id].name;
738 }
739
740 video::ITexture* TextureSource::getTexture(u32 id)
741 {
742         JMutexAutoLock lock(m_textureinfo_cache_mutex);
743
744         if(id >= m_textureinfo_cache.size())
745                 return NULL;
746
747         return m_textureinfo_cache[id].texture;
748 }
749
750 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
751 {
752         u32 actual_id = getTextureId(name);
753         if(id){
754                 *id = actual_id;
755         }
756         return getTexture(actual_id);
757 }
758
759 void TextureSource::processQueue()
760 {
761         /*
762                 Fetch textures
763         */
764         if(!m_get_texture_queue.empty())
765         {
766                 GetRequest<std::string, u32, u8, u8>
767                                 request = m_get_texture_queue.pop();
768
769                 /*infostream<<"TextureSource::processQueue(): "
770                                 <<"got texture request with "
771                                 <<"name=\""<<request.key<<"\""
772                                 <<std::endl;*/
773
774                 GetResult<std::string, u32, u8, u8>
775                                 result;
776                 result.key = request.key;
777                 result.callers = request.callers;
778                 result.item = getTextureIdDirect(request.key);
779
780                 request.dest->push_back(result);
781         }
782 }
783
784 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
785 {
786         //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
787         
788         assert(get_current_thread_id() == m_main_thread);
789         
790         m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
791         m_source_image_existence.set(name, true);
792 }
793         
794 void TextureSource::rebuildImagesAndTextures()
795 {
796         JMutexAutoLock lock(m_textureinfo_cache_mutex);
797
798         video::IVideoDriver* driver = m_device->getVideoDriver();
799
800         // Recreate textures
801         for(u32 i=0; i<m_textureinfo_cache.size(); i++){
802                 TextureInfo *ti = &m_textureinfo_cache[i];
803                 video::IImage *img =
804                         generate_image_from_scratch(ti->name, m_device, &m_sourcecache);
805                 // Create texture from resulting image
806                 video::ITexture *t = NULL;
807                 if(img)
808                         t = driver->addTexture(ti->name.c_str(), img);
809                 video::ITexture *t_old = ti->texture;
810                 // Replace texture
811                 ti->texture = t;
812                 ti->img = img;
813
814                 if (t_old != 0)
815                         m_texture_trash.push_back(t_old);
816         }
817 }
818
819 video::IImage* generate_image_from_scratch(std::string name,
820                 IrrlichtDevice *device, SourceImageCache *sourcecache)
821 {
822         /*infostream<<"generate_image_from_scratch(): "
823                         "\""<<name<<"\""<<std::endl;*/
824         
825         video::IVideoDriver* driver = device->getVideoDriver();
826         assert(driver);
827
828         /*
829                 Get the base image
830         */
831
832         video::IImage *baseimg = NULL;
833
834         char separator = '^';
835
836         // Find last meta separator in name
837         s32 last_separator_position = name.find_last_of(separator);
838         //if(last_separator_position == std::npos)
839         //      last_separator_position = -1;
840
841         /*infostream<<"generate_image_from_scratch(): "
842                         <<"last_separator_position="<<last_separator_position
843                         <<std::endl;*/
844
845         /*
846                 If separator was found, construct the base name and make the
847                 base image using a recursive call
848         */
849         std::string base_image_name;
850         if(last_separator_position != -1)
851         {
852                 // Construct base name
853                 base_image_name = name.substr(0, last_separator_position);
854                 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
855                                 " to get base image of \""<<name<<"\" = \""
856                 <<base_image_name<<"\""<<std::endl;*/
857                 baseimg = generate_image_from_scratch(base_image_name, device,
858                                 sourcecache);
859         }
860         
861         /*
862                 Parse out the last part of the name of the image and act
863                 according to it
864         */
865
866         std::string last_part_of_name = name.substr(last_separator_position+1);
867         //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
868         
869         // Generate image according to part of name
870         if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
871         {
872                 errorstream<<"generate_image_from_scratch(): "
873                                 "failed to generate \""<<last_part_of_name<<"\""
874                                 <<std::endl;
875                 return NULL;
876         }
877         
878         return baseimg;
879 }
880
881 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
882                 IrrlichtDevice *device, SourceImageCache *sourcecache)
883 {
884         video::IVideoDriver* driver = device->getVideoDriver();
885         assert(driver);
886
887         // Stuff starting with [ are special commands
888         if(part_of_name.size() == 0 || part_of_name[0] != '[')
889         {
890                 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
891
892                 if(image == NULL)
893                 {
894                         if(part_of_name != ""){
895                                 errorstream<<"generate_image(): Could not load image \""
896                                                 <<part_of_name<<"\""<<" while building texture"<<std::endl;
897                                 errorstream<<"generate_image(): Creating a dummy"
898                                                 <<" image for \""<<part_of_name<<"\""<<std::endl;
899                         }
900
901                         // Just create a dummy image
902                         //core::dimension2d<u32> dim(2,2);
903                         core::dimension2d<u32> dim(1,1);
904                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
905                         assert(image);
906                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
907                         image->setPixel(1,0, video::SColor(255,0,255,0));
908                         image->setPixel(0,1, video::SColor(255,0,0,255));
909                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
910                         image->setPixel(0,0, video::SColor(255,myrand()%256,
911                                         myrand()%256,myrand()%256));
912                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
913                                         myrand()%256,myrand()%256));
914                         image->setPixel(0,1, video::SColor(255,myrand()%256,
915                                         myrand()%256,myrand()%256));
916                         image->setPixel(1,1, video::SColor(255,myrand()%256,
917                                         myrand()%256,myrand()%256));*/
918                 }
919
920                 // If base image is NULL, load as base.
921                 if(baseimg == NULL)
922                 {
923                         //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
924                         /*
925                                 Copy it this way to get an alpha channel.
926                                 Otherwise images with alpha cannot be blitted on 
927                                 images that don't have alpha in the original file.
928                         */
929                         core::dimension2d<u32> dim = image->getDimension();
930                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
931                         image->copyTo(baseimg);
932                 }
933                 // Else blit on base.
934                 else
935                 {
936                         //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
937                         // Size of the copied area
938                         core::dimension2d<u32> dim = image->getDimension();
939                         //core::dimension2d<u32> dim(16,16);
940                         // Position to copy the blitted to in the base image
941                         core::position2d<s32> pos_to(0,0);
942                         // Position to copy the blitted from in the blitted image
943                         core::position2d<s32> pos_from(0,0);
944                         // Blit
945                         /*image->copyToWithAlpha(baseimg, pos_to,
946                                         core::rect<s32>(pos_from, dim),
947                                         video::SColor(255,255,255,255),
948                                         NULL);*/
949                         blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
950                 }
951                 //cleanup
952                 image->drop();
953         }
954         else
955         {
956                 // A special texture modification
957
958                 /*infostream<<"generate_image(): generating special "
959                                 <<"modification \""<<part_of_name<<"\""
960                                 <<std::endl;*/
961                 
962                 /*
963                         [crackN
964                         Adds a cracking texture
965                 */
966                 if(part_of_name.substr(0,6) == "[crack")
967                 {
968                         if(baseimg == NULL)
969                         {
970                                 errorstream<<"generate_image(): baseimg==NULL "
971                                                 <<"for part_of_name=\""<<part_of_name
972                                                 <<"\", cancelling."<<std::endl;
973                                 return false;
974                         }
975                         
976                         // Crack image number and overlay option
977                         s32 progression = 0;
978                         bool use_overlay = false;
979                         if(part_of_name.substr(6,1) == "o")
980                         {
981                                 progression = stoi(part_of_name.substr(7));
982                                 use_overlay = true;
983                         }
984                         else
985                         {
986                                 progression = stoi(part_of_name.substr(6));
987                                 use_overlay = false;
988                         }
989
990                         // Size of the base image
991                         core::dimension2d<u32> dim_base = baseimg->getDimension();
992                         
993                         /*
994                                 Load crack image.
995
996                                 It is an image with a number of cracking stages
997                                 horizontally tiled.
998                         */
999                         video::IImage *img_crack = sourcecache->getOrLoad(
1000                                         "crack_anylength.png", device);
1001                 
1002                         if(img_crack && progression >= 0)
1003                         {
1004                                 // Dimension of original image
1005                                 core::dimension2d<u32> dim_crack
1006                                                 = img_crack->getDimension();
1007                                 // Count of crack stages
1008                                 s32 crack_count = dim_crack.Height / dim_crack.Width;
1009                                 // Limit progression
1010                                 if(progression > crack_count-1)
1011                                         progression = crack_count-1;
1012                                 // Dimension of a single crack stage
1013                                 core::dimension2d<u32> dim_crack_cropped(
1014                                         dim_crack.Width,
1015                                         dim_crack.Width
1016                                 );
1017                                 // Create cropped and scaled crack images
1018                                 video::IImage *img_crack_cropped = driver->createImage(
1019                                                 video::ECF_A8R8G8B8, dim_crack_cropped);
1020                                 video::IImage *img_crack_scaled = driver->createImage(
1021                                                 video::ECF_A8R8G8B8, dim_base);
1022
1023                                 if(img_crack_cropped && img_crack_scaled)
1024                                 {
1025                                         // Crop crack image
1026                                         v2s32 pos_crack(0, progression*dim_crack.Width);
1027                                         img_crack->copyTo(img_crack_cropped,
1028                                                         v2s32(0,0),
1029                                                         core::rect<s32>(pos_crack, dim_crack_cropped));
1030                                         // Scale crack image by copying
1031                                         img_crack_cropped->copyToScaling(img_crack_scaled);
1032                                         // Copy or overlay crack image
1033                                         if(use_overlay)
1034                                         {
1035                                                 overlay(baseimg, img_crack_scaled);
1036                                         }
1037                                         else
1038                                         {
1039                                                 /*img_crack_scaled->copyToWithAlpha(
1040                                                                 baseimg,
1041                                                                 v2s32(0,0),
1042                                                                 core::rect<s32>(v2s32(0,0), dim_base),
1043                                                                 video::SColor(255,255,255,255));*/
1044                                                 blit_with_alpha(img_crack_scaled, baseimg,
1045                                                                 v2s32(0,0), v2s32(0,0), dim_base);
1046                                         }
1047                                 }
1048
1049                                 if(img_crack_scaled)
1050                                         img_crack_scaled->drop();
1051
1052                                 if(img_crack_cropped)
1053                                         img_crack_cropped->drop();
1054                                 
1055                                 img_crack->drop();
1056                         }
1057                 }
1058                 /*
1059                         [combine:WxH:X,Y=filename:X,Y=filename2
1060                         Creates a bigger texture from an amount of smaller ones
1061                 */
1062                 else if(part_of_name.substr(0,8) == "[combine")
1063                 {
1064                         Strfnd sf(part_of_name);
1065                         sf.next(":");
1066                         u32 w0 = stoi(sf.next("x"));
1067                         u32 h0 = stoi(sf.next(":"));
1068                         infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1069                         core::dimension2d<u32> dim(w0,h0);
1070                         if(baseimg == NULL)
1071                         {
1072                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1073                                 baseimg->fill(video::SColor(0,0,0,0));
1074                         }
1075                         while(sf.atend() == false)
1076                         {
1077                                 u32 x = stoi(sf.next(","));
1078                                 u32 y = stoi(sf.next("="));
1079                                 std::string filename = sf.next(":");
1080                                 infostream<<"Adding \""<<filename
1081                                                 <<"\" to combined ("<<x<<","<<y<<")"
1082                                                 <<std::endl;
1083                                 video::IImage *img = sourcecache->getOrLoad(filename, device);
1084                                 if(img)
1085                                 {
1086                                         core::dimension2d<u32> dim = img->getDimension();
1087                                         infostream<<"Size "<<dim.Width
1088                                                         <<"x"<<dim.Height<<std::endl;
1089                                         core::position2d<s32> pos_base(x, y);
1090                                         video::IImage *img2 =
1091                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
1092                                         img->copyTo(img2);
1093                                         img->drop();
1094                                         /*img2->copyToWithAlpha(baseimg, pos_base,
1095                                                         core::rect<s32>(v2s32(0,0), dim),
1096                                                         video::SColor(255,255,255,255),
1097                                                         NULL);*/
1098                                         blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1099                                         img2->drop();
1100                                 }
1101                                 else
1102                                 {
1103                                         infostream<<"img==NULL"<<std::endl;
1104                                 }
1105                         }
1106                 }
1107                 /*
1108                         "[brighten"
1109                 */
1110                 else if(part_of_name.substr(0,9) == "[brighten")
1111                 {
1112                         if(baseimg == NULL)
1113                         {
1114                                 errorstream<<"generate_image(): baseimg==NULL "
1115                                                 <<"for part_of_name=\""<<part_of_name
1116                                                 <<"\", cancelling."<<std::endl;
1117                                 return false;
1118                         }
1119
1120                         brighten(baseimg);
1121                 }
1122                 /*
1123                         "[noalpha"
1124                         Make image completely opaque.
1125                         Used for the leaves texture when in old leaves mode, so
1126                         that the transparent parts don't look completely black 
1127                         when simple alpha channel is used for rendering.
1128                 */
1129                 else if(part_of_name.substr(0,8) == "[noalpha")
1130                 {
1131                         if(baseimg == NULL)
1132                         {
1133                                 errorstream<<"generate_image(): baseimg==NULL "
1134                                                 <<"for part_of_name=\""<<part_of_name
1135                                                 <<"\", cancelling."<<std::endl;
1136                                 return false;
1137                         }
1138
1139                         core::dimension2d<u32> dim = baseimg->getDimension();
1140                         
1141                         // Set alpha to full
1142                         for(u32 y=0; y<dim.Height; y++)
1143                         for(u32 x=0; x<dim.Width; x++)
1144                         {
1145                                 video::SColor c = baseimg->getPixel(x,y);
1146                                 c.setAlpha(255);
1147                                 baseimg->setPixel(x,y,c);
1148                         }
1149                 }
1150                 /*
1151                         "[makealpha:R,G,B"
1152                         Convert one color to transparent.
1153                 */
1154                 else if(part_of_name.substr(0,11) == "[makealpha:")
1155                 {
1156                         if(baseimg == NULL)
1157                         {
1158                                 errorstream<<"generate_image(): baseimg==NULL "
1159                                                 <<"for part_of_name=\""<<part_of_name
1160                                                 <<"\", cancelling."<<std::endl;
1161                                 return false;
1162                         }
1163
1164                         Strfnd sf(part_of_name.substr(11));
1165                         u32 r1 = stoi(sf.next(","));
1166                         u32 g1 = stoi(sf.next(","));
1167                         u32 b1 = stoi(sf.next(""));
1168                         std::string filename = sf.next("");
1169
1170                         core::dimension2d<u32> dim = baseimg->getDimension();
1171                         
1172                         /*video::IImage *oldbaseimg = baseimg;
1173                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1174                         oldbaseimg->copyTo(baseimg);
1175                         oldbaseimg->drop();*/
1176
1177                         // Set alpha to full
1178                         for(u32 y=0; y<dim.Height; y++)
1179                         for(u32 x=0; x<dim.Width; x++)
1180                         {
1181                                 video::SColor c = baseimg->getPixel(x,y);
1182                                 u32 r = c.getRed();
1183                                 u32 g = c.getGreen();
1184                                 u32 b = c.getBlue();
1185                                 if(!(r == r1 && g == g1 && b == b1))
1186                                         continue;
1187                                 c.setAlpha(0);
1188                                 baseimg->setPixel(x,y,c);
1189                         }
1190                 }
1191                 /*
1192                         "[transformN"
1193                         Rotates and/or flips the image.
1194
1195                         N can be a number (between 0 and 7) or a transform name.
1196                         Rotations are counter-clockwise.
1197                         0  I      identity
1198                         1  R90    rotate by 90 degrees
1199                         2  R180   rotate by 180 degrees
1200                         3  R270   rotate by 270 degrees
1201                         4  FX     flip X
1202                         5  FXR90  flip X then rotate by 90 degrees
1203                         6  FY     flip Y
1204                         7  FYR90  flip Y then rotate by 90 degrees
1205
1206                         Note: Transform names can be concatenated to produce
1207                         their product (applies the first then the second).
1208                         The resulting transform will be equivalent to one of the
1209                         eight existing ones, though (see: dihedral group).
1210                 */
1211                 else if(part_of_name.substr(0,10) == "[transform")
1212                 {
1213                         if(baseimg == NULL)
1214                         {
1215                                 errorstream<<"generate_image(): baseimg==NULL "
1216                                                 <<"for part_of_name=\""<<part_of_name
1217                                                 <<"\", cancelling."<<std::endl;
1218                                 return false;
1219                         }
1220
1221                         u32 transform = parseImageTransform(part_of_name.substr(10));
1222                         core::dimension2d<u32> dim = imageTransformDimension(
1223                                         transform, baseimg->getDimension());
1224                         video::IImage *image = driver->createImage(
1225                                         baseimg->getColorFormat(), dim);
1226                         assert(image);
1227                         imageTransform(transform, baseimg, image);
1228                         baseimg->drop();
1229                         baseimg = image;
1230                 }
1231                 /*
1232                         [inventorycube{topimage{leftimage{rightimage
1233                         In every subimage, replace ^ with &.
1234                         Create an "inventory cube".
1235                         NOTE: This should be used only on its own.
1236                         Example (a grass block (not actually used in game):
1237                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1238                 */
1239                 else if(part_of_name.substr(0,14) == "[inventorycube")
1240                 {
1241                         if(baseimg != NULL)
1242                         {
1243                                 errorstream<<"generate_image(): baseimg!=NULL "
1244                                                 <<"for part_of_name=\""<<part_of_name
1245                                                 <<"\", cancelling."<<std::endl;
1246                                 return false;
1247                         }
1248
1249                         str_replace_char(part_of_name, '&', '^');
1250                         Strfnd sf(part_of_name);
1251                         sf.next("{");
1252                         std::string imagename_top = sf.next("{");
1253                         std::string imagename_left = sf.next("{");
1254                         std::string imagename_right = sf.next("{");
1255
1256                         // Generate images for the faces of the cube
1257                         video::IImage *img_top = generate_image_from_scratch(
1258                                         imagename_top, device, sourcecache);
1259                         video::IImage *img_left = generate_image_from_scratch(
1260                                         imagename_left, device, sourcecache);
1261                         video::IImage *img_right = generate_image_from_scratch(
1262                                         imagename_right, device, sourcecache);
1263                         assert(img_top && img_left && img_right);
1264
1265                         // Create textures from images
1266                         video::ITexture *texture_top = driver->addTexture(
1267                                         (imagename_top + "__temp__").c_str(), img_top);
1268                         video::ITexture *texture_left = driver->addTexture(
1269                                         (imagename_left + "__temp__").c_str(), img_left);
1270                         video::ITexture *texture_right = driver->addTexture(
1271                                         (imagename_right + "__temp__").c_str(), img_right);
1272                         assert(texture_top && texture_left && texture_right);
1273
1274                         // Drop images
1275                         img_top->drop();
1276                         img_left->drop();
1277                         img_right->drop();
1278                         
1279                         /*
1280                                 Draw a cube mesh into a render target texture
1281                         */
1282                         scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1283                         setMeshColor(cube, video::SColor(255, 255, 255, 255));
1284                         cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1285                         cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1286                         cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1287                         cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1288                         cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1289                         cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1290
1291                         core::dimension2d<u32> dim(64,64);
1292                         std::string rtt_texture_name = part_of_name + "_RTT";
1293
1294                         v3f camera_position(0, 1.0, -1.5);
1295                         camera_position.rotateXZBy(45);
1296                         v3f camera_lookat(0, 0, 0);
1297                         core::CMatrix4<f32> camera_projection_matrix;
1298                         // Set orthogonal projection
1299                         camera_projection_matrix.buildProjectionMatrixOrthoLH(
1300                                         1.65, 1.65, 0, 100);
1301
1302                         video::SColorf ambient_light(0.2,0.2,0.2);
1303                         v3f light_position(10, 100, -50);
1304                         video::SColorf light_color(0.5,0.5,0.5);
1305                         f32 light_radius = 1000;
1306
1307                         video::ITexture *rtt = generateTextureFromMesh(
1308                                         cube, device, dim, rtt_texture_name,
1309                                         camera_position,
1310                                         camera_lookat,
1311                                         camera_projection_matrix,
1312                                         ambient_light,
1313                                         light_position,
1314                                         light_color,
1315                                         light_radius);
1316                         
1317                         // Drop mesh
1318                         cube->drop();
1319
1320                         // Free textures of images
1321                         driver->removeTexture(texture_top);
1322                         driver->removeTexture(texture_left);
1323                         driver->removeTexture(texture_right);
1324                         
1325                         if(rtt == NULL)
1326                         {
1327                                 baseimg = generate_image_from_scratch(
1328                                                 imagename_top, device, sourcecache);
1329                                 return true;
1330                         }
1331
1332                         // Create image of render target
1333                         video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1334                         assert(image);
1335
1336                         //cleanup texture
1337                         driver->removeTexture(rtt);
1338
1339                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1340
1341                         if(image)
1342                         {
1343                                 image->copyTo(baseimg);
1344                                 image->drop();
1345                         }
1346                 }
1347                 /*
1348                         [lowpart:percent:filename
1349                         Adds the lower part of a texture
1350                 */
1351                 else if(part_of_name.substr(0,9) == "[lowpart:")
1352                 {
1353                         Strfnd sf(part_of_name);
1354                         sf.next(":");
1355                         u32 percent = stoi(sf.next(":"));
1356                         std::string filename = sf.next(":");
1357                         //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1358
1359                         if(baseimg == NULL)
1360                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1361                         video::IImage *img = sourcecache->getOrLoad(filename, device);
1362                         if(img)
1363                         {
1364                                 core::dimension2d<u32> dim = img->getDimension();
1365                                 core::position2d<s32> pos_base(0, 0);
1366                                 video::IImage *img2 =
1367                                                 driver->createImage(video::ECF_A8R8G8B8, dim);
1368                                 img->copyTo(img2);
1369                                 img->drop();
1370                                 core::position2d<s32> clippos(0, 0);
1371                                 clippos.Y = dim.Height * (100-percent) / 100;
1372                                 core::dimension2d<u32> clipdim = dim;
1373                                 clipdim.Height = clipdim.Height * percent / 100 + 1;
1374                                 core::rect<s32> cliprect(clippos, clipdim);
1375                                 img2->copyToWithAlpha(baseimg, pos_base,
1376                                                 core::rect<s32>(v2s32(0,0), dim),
1377                                                 video::SColor(255,255,255,255),
1378                                                 &cliprect);
1379                                 img2->drop();
1380                         }
1381                 }
1382                 /*
1383                         [verticalframe:N:I
1384                         Crops a frame of a vertical animation.
1385                         N = frame count, I = frame index
1386                 */
1387                 else if(part_of_name.substr(0,15) == "[verticalframe:")
1388                 {
1389                         Strfnd sf(part_of_name);
1390                         sf.next(":");
1391                         u32 frame_count = stoi(sf.next(":"));
1392                         u32 frame_index = stoi(sf.next(":"));
1393
1394                         if(baseimg == NULL){
1395                                 errorstream<<"generate_image(): baseimg!=NULL "
1396                                                 <<"for part_of_name=\""<<part_of_name
1397                                                 <<"\", cancelling."<<std::endl;
1398                                 return false;
1399                         }
1400                         
1401                         v2u32 frame_size = baseimg->getDimension();
1402                         frame_size.Y /= frame_count;
1403
1404                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1405                                         frame_size);
1406                         if(!img){
1407                                 errorstream<<"generate_image(): Could not create image "
1408                                                 <<"for part_of_name=\""<<part_of_name
1409                                                 <<"\", cancelling."<<std::endl;
1410                                 return false;
1411                         }
1412
1413                         // Fill target image with transparency
1414                         img->fill(video::SColor(0,0,0,0));
1415
1416                         core::dimension2d<u32> dim = frame_size;
1417                         core::position2d<s32> pos_dst(0, 0);
1418                         core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1419                         baseimg->copyToWithAlpha(img, pos_dst,
1420                                         core::rect<s32>(pos_src, dim),
1421                                         video::SColor(255,255,255,255),
1422                                         NULL);
1423                         // Replace baseimg
1424                         baseimg->drop();
1425                         baseimg = img;
1426                 }
1427                 else
1428                 {
1429                         errorstream<<"generate_image(): Invalid "
1430                                         " modification: \""<<part_of_name<<"\""<<std::endl;
1431                 }
1432         }
1433
1434         return true;
1435 }
1436
1437 void overlay(video::IImage *image, video::IImage *overlay)
1438 {
1439         /*
1440                 Copy overlay to image, taking alpha into account.
1441                 Where image is transparent, don't copy from overlay.
1442                 Images sizes must be identical.
1443         */
1444         if(image == NULL || overlay == NULL)
1445                 return;
1446         
1447         core::dimension2d<u32> dim = image->getDimension();
1448         core::dimension2d<u32> dim_overlay = overlay->getDimension();
1449         assert(dim == dim_overlay);
1450
1451         for(u32 y=0; y<dim.Height; y++)
1452         for(u32 x=0; x<dim.Width; x++)
1453         {
1454                 video::SColor c1 = image->getPixel(x,y);
1455                 video::SColor c2 = overlay->getPixel(x,y);
1456                 u32 a1 = c1.getAlpha();
1457                 u32 a2 = c2.getAlpha();
1458                 if(a1 == 255 && a2 != 0)
1459                 {
1460                         c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1461                         c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1462                         c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1463                 }
1464                 image->setPixel(x,y,c1);
1465         }
1466 }
1467
1468 /*
1469         Draw an image on top of an another one, using the alpha channel of the
1470         source image
1471
1472         This exists because IImage::copyToWithAlpha() doesn't seem to always
1473         work.
1474 */
1475 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1476                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1477 {
1478         for(u32 y0=0; y0<size.Y; y0++)
1479         for(u32 x0=0; x0<size.X; x0++)
1480         {
1481                 s32 src_x = src_pos.X + x0;
1482                 s32 src_y = src_pos.Y + y0;
1483                 s32 dst_x = dst_pos.X + x0;
1484                 s32 dst_y = dst_pos.Y + y0;
1485                 video::SColor src_c = src->getPixel(src_x, src_y);
1486                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1487                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1488                 dst->setPixel(dst_x, dst_y, dst_c);
1489         }
1490 }
1491
1492 void brighten(video::IImage *image)
1493 {
1494         if(image == NULL)
1495                 return;
1496         
1497         core::dimension2d<u32> dim = image->getDimension();
1498
1499         for(u32 y=0; y<dim.Height; y++)
1500         for(u32 x=0; x<dim.Width; x++)
1501         {
1502                 video::SColor c = image->getPixel(x,y);
1503                 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1504                 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1505                 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1506                 image->setPixel(x,y,c);
1507         }
1508 }
1509
1510 u32 parseImageTransform(const std::string& s)
1511 {
1512         int total_transform = 0;
1513
1514         std::string transform_names[8];
1515         transform_names[0] = "i";
1516         transform_names[1] = "r90";
1517         transform_names[2] = "r180";
1518         transform_names[3] = "r270";
1519         transform_names[4] = "fx";
1520         transform_names[6] = "fy";
1521
1522         std::size_t pos = 0;
1523         while(pos < s.size())
1524         {
1525                 int transform = -1;
1526                 for(int i = 0; i <= 7; ++i)
1527                 {
1528                         const std::string &name_i = transform_names[i];
1529
1530                         if(s[pos] == ('0' + i))
1531                         {
1532                                 transform = i;
1533                                 pos++;
1534                                 break;
1535                         }
1536                         else if(!(name_i.empty()) &&
1537                                 lowercase(s.substr(pos, name_i.size())) == name_i)
1538                         {
1539                                 transform = i;
1540                                 pos += name_i.size();
1541                                 break;
1542                         }
1543                 }
1544                 if(transform < 0)
1545                         break;
1546
1547                 // Multiply total_transform and transform in the group D4
1548                 int new_total = 0;
1549                 if(transform < 4)
1550                         new_total = (transform + total_transform) % 4;
1551                 else
1552                         new_total = (transform - total_transform + 8) % 4;
1553                 if((transform >= 4) ^ (total_transform >= 4))
1554                         new_total += 4;
1555
1556                 total_transform = new_total;
1557         }
1558         return total_transform;
1559 }
1560
1561 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1562 {
1563         if(transform % 2 == 0)
1564                 return dim;
1565         else
1566                 return core::dimension2d<u32>(dim.Height, dim.Width);
1567 }
1568
1569 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1570 {
1571         if(src == NULL || dst == NULL)
1572                 return;
1573         
1574         core::dimension2d<u32> srcdim = src->getDimension();
1575         core::dimension2d<u32> dstdim = dst->getDimension();
1576
1577         assert(dstdim == imageTransformDimension(transform, srcdim));
1578         assert(transform >= 0 && transform <= 7);
1579
1580         /*
1581                 Compute the transformation from source coordinates (sx,sy)
1582                 to destination coordinates (dx,dy).
1583         */
1584         int sxn = 0;
1585         int syn = 2;
1586         if(transform == 0)         // identity
1587                 sxn = 0, syn = 2;  //   sx = dx, sy = dy
1588         else if(transform == 1)    // rotate by 90 degrees ccw
1589                 sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
1590         else if(transform == 2)    // rotate by 180 degrees
1591                 sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
1592         else if(transform == 3)    // rotate by 270 degrees ccw
1593                 sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
1594         else if(transform == 4)    // flip x
1595                 sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
1596         else if(transform == 5)    // flip x then rotate by 90 degrees ccw
1597                 sxn = 2, syn = 0;  //   sx = dy, sy = dx
1598         else if(transform == 6)    // flip y
1599                 sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
1600         else if(transform == 7)    // flip y then rotate by 90 degrees ccw
1601                 sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
1602
1603         for(u32 dy=0; dy<dstdim.Height; dy++)
1604         for(u32 dx=0; dx<dstdim.Width; dx++)
1605         {
1606                 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1607                 u32 sx = entries[sxn];
1608                 u32 sy = entries[syn];
1609                 video::SColor c = src->getPixel(sx,sy);
1610                 dst->setPixel(dx,dy,c);
1611         }
1612 }