]> git.lizzy.rs Git - minetest.git/blob - src/client/tile.cpp
Modernize client code (#6250)
[minetest.git] / src / client / 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
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "filesys.h"
27 #include "settings.h"
28 #include "mesh.h"
29 #include "gamedef.h"
30 #include "util/strfnd.h"
31 #include "imagefilters.h"
32 #include "guiscalingfilter.h"
33 #include "renderingengine.h"
34
35
36 #ifdef __ANDROID__
37 #include <GLES/gl.h>
38 #endif
39
40 /*
41         A cache from texture name to texture path
42 */
43 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
44
45 /*
46         Replaces the filename extension.
47         eg:
48                 std::string image = "a/image.png"
49                 replace_ext(image, "jpg")
50                 -> image = "a/image.jpg"
51         Returns true on success.
52 */
53 static bool replace_ext(std::string &path, const char *ext)
54 {
55         if (ext == NULL)
56                 return false;
57         // Find place of last dot, fail if \ or / found.
58         s32 last_dot_i = -1;
59         for (s32 i=path.size()-1; i>=0; i--)
60         {
61                 if (path[i] == '.')
62                 {
63                         last_dot_i = i;
64                         break;
65                 }
66
67                 if (path[i] == '\\' || path[i] == '/')
68                         break;
69         }
70         // If not found, return an empty string
71         if (last_dot_i == -1)
72                 return false;
73         // Else make the new path
74         path = path.substr(0, last_dot_i+1) + ext;
75         return true;
76 }
77
78 /*
79         Find out the full path of an image by trying different filename
80         extensions.
81
82         If failed, return "".
83 */
84 std::string getImagePath(std::string path)
85 {
86         // A NULL-ended list of possible image extensions
87         const char *extensions[] = {
88                 "png", "jpg", "bmp", "tga",
89                 "pcx", "ppm", "psd", "wal", "rgb",
90                 NULL
91         };
92         // If there is no extension, add one
93         if (removeStringEnd(path, extensions).empty())
94                 path = path + ".png";
95         // Check paths until something is found to exist
96         const char **ext = extensions;
97         do{
98                 bool r = replace_ext(path, *ext);
99                 if (!r)
100                         return "";
101                 if (fs::PathExists(path))
102                         return path;
103         }
104         while((++ext) != NULL);
105
106         return "";
107 }
108
109 /*
110         Gets the path to a texture by first checking if the texture exists
111         in texture_path and if not, using the data path.
112
113         Checks all supported extensions by replacing the original extension.
114
115         If not found, returns "".
116
117         Utilizes a thread-safe cache.
118 */
119 std::string getTexturePath(const std::string &filename)
120 {
121         std::string fullpath;
122         /*
123                 Check from cache
124         */
125         bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
126         if (incache)
127                 return fullpath;
128
129         /*
130                 Check from texture_path
131         */
132         const std::string &texture_path = g_settings->get("texture_path");
133         if (!texture_path.empty()) {
134                 std::string testpath = texture_path + DIR_DELIM + filename;
135                 // Check all filename extensions. Returns "" if not found.
136                 fullpath = getImagePath(testpath);
137         }
138
139         /*
140                 Check from default data directory
141         */
142         if (fullpath.empty())
143         {
144                 std::string base_path = porting::path_share + DIR_DELIM + "textures"
145                                 + DIR_DELIM + "base" + DIR_DELIM + "pack";
146                 std::string testpath = base_path + DIR_DELIM + filename;
147                 // Check all filename extensions. Returns "" if not found.
148                 fullpath = getImagePath(testpath);
149         }
150
151         // Add to cache (also an empty result is cached)
152         g_texturename_to_path_cache.set(filename, fullpath);
153
154         // Finally return it
155         return fullpath;
156 }
157
158 void clearTextureNameCache()
159 {
160         g_texturename_to_path_cache.clear();
161 }
162
163 /*
164         Stores internal information about a texture.
165 */
166
167 struct TextureInfo
168 {
169         std::string name;
170         video::ITexture *texture;
171
172         TextureInfo(
173                         const std::string &name_,
174                         video::ITexture *texture_=NULL
175                 ):
176                 name(name_),
177                 texture(texture_)
178         {
179         }
180 };
181
182 /*
183         SourceImageCache: A cache used for storing source images.
184 */
185
186 class SourceImageCache
187 {
188 public:
189         ~SourceImageCache() {
190                 for (auto &m_image : m_images) {
191                         m_image.second->drop();
192                 }
193                 m_images.clear();
194         }
195         void insert(const std::string &name, video::IImage *img, bool prefer_local)
196         {
197                 assert(img); // Pre-condition
198                 // Remove old image
199                 std::map<std::string, video::IImage*>::iterator n;
200                 n = m_images.find(name);
201                 if (n != m_images.end()){
202                         if (n->second)
203                                 n->second->drop();
204                 }
205
206                 video::IImage* toadd = img;
207                 bool need_to_grab = true;
208
209                 // Try to use local texture instead if asked to
210                 if (prefer_local){
211                         std::string path = getTexturePath(name);
212                         if (!path.empty()) {
213                                 video::IImage *img2 = RenderingEngine::get_video_driver()->
214                                         createImageFromFile(path.c_str());
215                                 if (img2){
216                                         toadd = img2;
217                                         need_to_grab = false;
218                                 }
219                         }
220                 }
221
222                 if (need_to_grab)
223                         toadd->grab();
224                 m_images[name] = toadd;
225         }
226         video::IImage* get(const std::string &name)
227         {
228                 std::map<std::string, video::IImage*>::iterator n;
229                 n = m_images.find(name);
230                 if (n != m_images.end())
231                         return n->second;
232                 return NULL;
233         }
234         // Primarily fetches from cache, secondarily tries to read from filesystem
235         video::IImage *getOrLoad(const std::string &name)
236         {
237                 std::map<std::string, video::IImage*>::iterator n;
238                 n = m_images.find(name);
239                 if (n != m_images.end()){
240                         n->second->grab(); // Grab for caller
241                         return n->second;
242                 }
243                 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
244                 std::string path = getTexturePath(name);
245                 if (path.empty()) {
246                         infostream<<"SourceImageCache::getOrLoad(): No path found for \""
247                                         <<name<<"\""<<std::endl;
248                         return NULL;
249                 }
250                 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
251                                 <<"\""<<std::endl;
252                 video::IImage *img = driver->createImageFromFile(path.c_str());
253
254                 if (img){
255                         m_images[name] = img;
256                         img->grab(); // Grab for caller
257                 }
258                 return img;
259         }
260 private:
261         std::map<std::string, video::IImage*> m_images;
262 };
263
264 /*
265         TextureSource
266 */
267
268 class TextureSource : public IWritableTextureSource
269 {
270 public:
271         TextureSource();
272         virtual ~TextureSource();
273
274         /*
275                 Example case:
276                 Now, assume a texture with the id 1 exists, and has the name
277                 "stone.png^mineral1".
278                 Then a random thread calls getTextureId for a texture called
279                 "stone.png^mineral1^crack0".
280                 ...Now, WTF should happen? Well:
281                 - getTextureId strips off stuff recursively from the end until
282                   the remaining part is found, or nothing is left when
283                   something is stripped out
284
285                 But it is slow to search for textures by names and modify them
286                 like that?
287                 - ContentFeatures is made to contain ids for the basic plain
288                   textures
289                 - Crack textures can be slow by themselves, but the framework
290                   must be fast.
291
292                 Example case #2:
293                 - Assume a texture with the id 1 exists, and has the name
294                   "stone.png^mineral_coal.png".
295                 - Now getNodeTile() stumbles upon a node which uses
296                   texture id 1, and determines that MATERIAL_FLAG_CRACK
297                   must be applied to the tile
298                 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
299                   has received the current crack level 0 from the client. It
300                   finds out the name of the texture with getTextureName(1),
301                   appends "^crack0" to it and gets a new texture id with
302                   getTextureId("stone.png^mineral_coal.png^crack0").
303
304         */
305
306         /*
307                 Gets a texture id from cache or
308                 - if main thread, generates the texture, adds to cache and returns id.
309                 - if other thread, adds to request queue and waits for main thread.
310
311                 The id 0 points to a NULL texture. It is returned in case of error.
312         */
313         u32 getTextureId(const std::string &name);
314
315         // Finds out the name of a cached texture.
316         std::string getTextureName(u32 id);
317
318         /*
319                 If texture specified by the name pointed by the id doesn't
320                 exist, create it, then return the cached texture.
321
322                 Can be called from any thread. If called from some other thread
323                 and not found in cache, the call is queued to the main thread
324                 for processing.
325         */
326         video::ITexture* getTexture(u32 id);
327
328         video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
329
330         /*
331                 Get a texture specifically intended for mesh
332                 application, i.e. not HUD, compositing, or other 2D
333                 use.  This texture may be a different size and may
334                 have had additional filters applied.
335         */
336         video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
337
338         virtual Palette* getPalette(const std::string &name);
339
340         bool isKnownSourceImage(const std::string &name)
341         {
342                 bool is_known = false;
343                 bool cache_found = m_source_image_existence.get(name, &is_known);
344                 if (cache_found)
345                         return is_known;
346                 // Not found in cache; find out if a local file exists
347                 is_known = (!getTexturePath(name).empty());
348                 m_source_image_existence.set(name, is_known);
349                 return is_known;
350         }
351
352         // Processes queued texture requests from other threads.
353         // Shall be called from the main thread.
354         void processQueue();
355
356         // Insert an image into the cache without touching the filesystem.
357         // Shall be called from the main thread.
358         void insertSourceImage(const std::string &name, video::IImage *img);
359
360         // Rebuild images and textures from the current set of source images
361         // Shall be called from the main thread.
362         void rebuildImagesAndTextures();
363
364         // Render a mesh to a texture.
365         // Returns NULL if render-to-texture failed.
366         // Shall be called from the main thread.
367         video::ITexture* generateTextureFromMesh(
368                         const TextureFromMeshParams &params);
369
370         video::ITexture* getNormalTexture(const std::string &name);
371         video::SColor getTextureAverageColor(const std::string &name);
372         video::ITexture *getShaderFlagsTexture(bool normamap_present);
373
374 private:
375
376         // The id of the thread that is allowed to use irrlicht directly
377         std::thread::id m_main_thread;
378
379         // Cache of source images
380         // This should be only accessed from the main thread
381         SourceImageCache m_sourcecache;
382
383         // Generate a texture
384         u32 generateTexture(const std::string &name);
385
386         // Generate image based on a string like "stone.png" or "[crack:1:0".
387         // if baseimg is NULL, it is created. Otherwise stuff is made on it.
388         bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
389
390         /*! Generates an image from a full string like
391          * "stone.png^mineral_coal.png^[crack:1:0".
392          * Shall be called from the main thread.
393          * The returned Image should be dropped.
394          */
395         video::IImage* generateImage(const std::string &name);
396
397         // Thread-safe cache of what source images are known (true = known)
398         MutexedMap<std::string, bool> m_source_image_existence;
399
400         // A texture id is index in this array.
401         // The first position contains a NULL texture.
402         std::vector<TextureInfo> m_textureinfo_cache;
403         // Maps a texture name to an index in the former.
404         std::map<std::string, u32> m_name_to_id;
405         // The two former containers are behind this mutex
406         std::mutex m_textureinfo_cache_mutex;
407
408         // Queued texture fetches (to be processed by the main thread)
409         RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
410
411         // Textures that have been overwritten with other ones
412         // but can't be deleted because the ITexture* might still be used
413         std::vector<video::ITexture*> m_texture_trash;
414
415         // Maps image file names to loaded palettes.
416         std::unordered_map<std::string, Palette> m_palettes;
417
418         // Cached settings needed for making textures from meshes
419         bool m_setting_trilinear_filter;
420         bool m_setting_bilinear_filter;
421         bool m_setting_anisotropic_filter;
422 };
423
424 IWritableTextureSource *createTextureSource()
425 {
426         return new TextureSource();
427 }
428
429 TextureSource::TextureSource()
430 {
431         m_main_thread = std::this_thread::get_id();
432
433         // Add a NULL TextureInfo as the first index, named ""
434         m_textureinfo_cache.emplace_back("");
435         m_name_to_id[""] = 0;
436
437         // Cache some settings
438         // Note: Since this is only done once, the game must be restarted
439         // for these settings to take effect
440         m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
441         m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
442         m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
443 }
444
445 TextureSource::~TextureSource()
446 {
447         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
448
449         unsigned int textures_before = driver->getTextureCount();
450
451         for (const auto &iter : m_textureinfo_cache) {
452                 //cleanup texture
453                 if (iter.texture)
454                         driver->removeTexture(iter.texture);
455         }
456         m_textureinfo_cache.clear();
457
458         for (auto t : m_texture_trash) {
459                 //cleanup trashed texture
460                 driver->removeTexture(t);
461         }
462
463         infostream << "~TextureSource() "<< textures_before << "/"
464                         << driver->getTextureCount() << std::endl;
465 }
466
467 u32 TextureSource::getTextureId(const std::string &name)
468 {
469         //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
470
471         {
472                 /*
473                         See if texture already exists
474                 */
475                 MutexAutoLock lock(m_textureinfo_cache_mutex);
476                 std::map<std::string, u32>::iterator n;
477                 n = m_name_to_id.find(name);
478                 if (n != m_name_to_id.end())
479                 {
480                         return n->second;
481                 }
482         }
483
484         /*
485                 Get texture
486         */
487         if (std::this_thread::get_id() == m_main_thread)
488         {
489                 return generateTexture(name);
490         }
491         else
492         {
493                 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
494
495                 // We're gonna ask the result to be put into here
496                 static ResultQueue<std::string, u32, u8, u8> result_queue;
497
498                 // Throw a request in
499                 m_get_texture_queue.add(name, 0, 0, &result_queue);
500
501                 /*infostream<<"Waiting for texture from main thread, name=\""
502                                 <<name<<"\""<<std::endl;*/
503
504                 try
505                 {
506                         while(true) {
507                                 // Wait result for a second
508                                 GetResult<std::string, u32, u8, u8>
509                                         result = result_queue.pop_front(1000);
510
511                                 if (result.key == name) {
512                                         return result.item;
513                                 }
514                         }
515                 }
516                 catch(ItemNotFoundException &e)
517                 {
518                         errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
519                         return 0;
520                 }
521         }
522
523         infostream<<"getTextureId(): Failed"<<std::endl;
524
525         return 0;
526 }
527
528 // Draw an image on top of an another one, using the alpha channel of the
529 // source image
530 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
531                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
532
533 // Like blit_with_alpha, but only modifies destination pixels that
534 // are fully opaque
535 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
536                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
537
538 // Apply a color to an image.  Uses an int (0-255) to calculate the ratio.
539 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
540 // color alpha with the destination alpha.
541 // Otherwise, any pixels that are not fully transparent get the color alpha.
542 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
543                 const video::SColor &color, int ratio, bool keep_alpha);
544
545 // paint a texture using the given color
546 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
547                 const video::SColor &color);
548
549 // Apply a mask to an image
550 static void apply_mask(video::IImage *mask, video::IImage *dst,
551                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
552
553 // Draw or overlay a crack
554 static void draw_crack(video::IImage *crack, video::IImage *dst,
555                 bool use_overlay, s32 frame_count, s32 progression,
556                 video::IVideoDriver *driver);
557
558 // Brighten image
559 void brighten(video::IImage *image);
560 // Parse a transform name
561 u32 parseImageTransform(const std::string& s);
562 // Apply transform to image dimension
563 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
564 // Apply transform to image data
565 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
566
567 /*
568         This method generates all the textures
569 */
570 u32 TextureSource::generateTexture(const std::string &name)
571 {
572         //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
573
574         // Empty name means texture 0
575         if (name.empty()) {
576                 infostream<<"generateTexture(): name is empty"<<std::endl;
577                 return 0;
578         }
579
580         {
581                 /*
582                         See if texture already exists
583                 */
584                 MutexAutoLock lock(m_textureinfo_cache_mutex);
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                         return n->second;
589                 }
590         }
591
592         /*
593                 Calling only allowed from main thread
594         */
595         if (std::this_thread::get_id() != m_main_thread) {
596                 errorstream<<"TextureSource::generateTexture() "
597                                 "called not from main thread"<<std::endl;
598                 return 0;
599         }
600
601         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
602         sanity_check(driver);
603
604         video::IImage *img = generateImage(name);
605
606         video::ITexture *tex = NULL;
607
608         if (img != NULL) {
609 #ifdef __ANDROID__
610                 img = Align2Npot2(img, driver);
611 #endif
612                 // Create texture from resulting image
613                 tex = driver->addTexture(name.c_str(), img);
614                 guiScalingCache(io::path(name.c_str()), driver, img);
615                 img->drop();
616         }
617
618         /*
619                 Add texture to caches (add NULL textures too)
620         */
621
622         MutexAutoLock lock(m_textureinfo_cache_mutex);
623
624         u32 id = m_textureinfo_cache.size();
625         TextureInfo ti(name, tex);
626         m_textureinfo_cache.push_back(ti);
627         m_name_to_id[name] = id;
628
629         return id;
630 }
631
632 std::string TextureSource::getTextureName(u32 id)
633 {
634         MutexAutoLock lock(m_textureinfo_cache_mutex);
635
636         if (id >= m_textureinfo_cache.size())
637         {
638                 errorstream<<"TextureSource::getTextureName(): id="<<id
639                                 <<" >= m_textureinfo_cache.size()="
640                                 <<m_textureinfo_cache.size()<<std::endl;
641                 return "";
642         }
643
644         return m_textureinfo_cache[id].name;
645 }
646
647 video::ITexture* TextureSource::getTexture(u32 id)
648 {
649         MutexAutoLock lock(m_textureinfo_cache_mutex);
650
651         if (id >= m_textureinfo_cache.size())
652                 return NULL;
653
654         return m_textureinfo_cache[id].texture;
655 }
656
657 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
658 {
659         u32 actual_id = getTextureId(name);
660         if (id){
661                 *id = actual_id;
662         }
663         return getTexture(actual_id);
664 }
665
666 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
667 {
668         return getTexture(name + "^[applyfiltersformesh", id);
669 }
670
671 Palette* TextureSource::getPalette(const std::string &name)
672 {
673         // Only the main thread may load images
674         sanity_check(std::this_thread::get_id() == m_main_thread);
675
676         if (name == "")
677                 return NULL;
678
679         auto it = m_palettes.find(name);
680         if (it == m_palettes.end()) {
681                 // Create palette
682                 video::IImage *img = generateImage(name);
683                 if (!img) {
684                         warningstream << "TextureSource::getPalette(): palette \"" << name
685                                 << "\" could not be loaded." << std::endl;
686                         return NULL;
687                 }
688                 Palette new_palette;
689                 u32 w = img->getDimension().Width;
690                 u32 h = img->getDimension().Height;
691                 // Real area of the image
692                 u32 area = h * w;
693                 if (area == 0)
694                         return NULL;
695                 if (area > 256) {
696                         warningstream << "TextureSource::getPalette(): the specified"
697                                 << " palette image \"" << name << "\" is larger than 256"
698                                 << " pixels, using the first 256." << std::endl;
699                         area = 256;
700                 } else if (256 % area != 0)
701                         warningstream << "TextureSource::getPalette(): the "
702                                 << "specified palette image \"" << name << "\" does not "
703                                 << "contain power of two pixels." << std::endl;
704                 // We stretch the palette so it will fit 256 values
705                 // This many param2 values will have the same color
706                 u32 step = 256 / area;
707                 // For each pixel in the image
708                 for (u32 i = 0; i < area; i++) {
709                         video::SColor c = img->getPixel(i % w, i / w);
710                         // Fill in palette with 'step' colors
711                         for (u32 j = 0; j < step; j++)
712                                 new_palette.push_back(c);
713                 }
714                 img->drop();
715                 // Fill in remaining elements
716                 while (new_palette.size() < 256)
717                         new_palette.emplace_back(0xFFFFFFFF);
718                 m_palettes[name] = new_palette;
719                 it = m_palettes.find(name);
720         }
721         if (it != m_palettes.end())
722                 return &((*it).second);
723         return NULL;
724 }
725
726 void TextureSource::processQueue()
727 {
728         /*
729                 Fetch textures
730         */
731         //NOTE this is only thread safe for ONE consumer thread!
732         if (!m_get_texture_queue.empty())
733         {
734                 GetRequest<std::string, u32, u8, u8>
735                                 request = m_get_texture_queue.pop();
736
737                 /*infostream<<"TextureSource::processQueue(): "
738                                 <<"got texture request with "
739                                 <<"name=\""<<request.key<<"\""
740                                 <<std::endl;*/
741
742                 m_get_texture_queue.pushResult(request, generateTexture(request.key));
743         }
744 }
745
746 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
747 {
748         //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
749
750         sanity_check(std::this_thread::get_id() == m_main_thread);
751
752         m_sourcecache.insert(name, img, true);
753         m_source_image_existence.set(name, true);
754 }
755
756 void TextureSource::rebuildImagesAndTextures()
757 {
758         MutexAutoLock lock(m_textureinfo_cache_mutex);
759
760         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
761         sanity_check(driver);
762
763         // Recreate textures
764         for (TextureInfo &ti : m_textureinfo_cache) {
765                 video::IImage *img = generateImage(ti.name);
766 #ifdef __ANDROID__
767                 img = Align2Npot2(img, driver);
768 #endif
769                 // Create texture from resulting image
770                 video::ITexture *t = NULL;
771                 if (img) {
772                         t = driver->addTexture(ti.name.c_str(), img);
773                         guiScalingCache(io::path(ti.name.c_str()), driver, img);
774                         img->drop();
775                 }
776                 video::ITexture *t_old = ti.texture;
777                 // Replace texture
778                 ti.texture = t;
779
780                 if (t_old)
781                         m_texture_trash.push_back(t_old);
782         }
783 }
784
785 video::ITexture* TextureSource::generateTextureFromMesh(
786                 const TextureFromMeshParams &params)
787 {
788         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
789         sanity_check(driver);
790
791 #ifdef __ANDROID__
792         const GLubyte* renderstr = glGetString(GL_RENDERER);
793         std::string renderer((char*) renderstr);
794
795         // use no render to texture hack
796         if (
797                 (renderer.find("Adreno") != std::string::npos) ||
798                 (renderer.find("Mali") != std::string::npos) ||
799                 (renderer.find("Immersion") != std::string::npos) ||
800                 (renderer.find("Tegra") != std::string::npos) ||
801                 g_settings->getBool("inventory_image_hack")
802                 ) {
803                 // Get a scene manager
804                 scene::ISceneManager *smgr_main = m_device->getSceneManager();
805                 sanity_check(smgr_main);
806                 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
807                 sanity_check(smgr);
808
809                 const float scaling = 0.2;
810
811                 scene::IMeshSceneNode* meshnode =
812                                 smgr->addMeshSceneNode(params.mesh, NULL,
813                                                 -1, v3f(0,0,0), v3f(0,0,0),
814                                                 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
815                 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
816                 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
817                 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
818                 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
819                 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
820
821                 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
822                                 params.camera_position, params.camera_lookat);
823                 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
824                 camera->setProjectionMatrix(params.camera_projection_matrix, false);
825
826                 smgr->setAmbientLight(params.ambient_light);
827                 smgr->addLightSceneNode(0,
828                                 params.light_position,
829                                 params.light_color,
830                                 params.light_radius*scaling);
831
832                 core::dimension2d<u32> screen = driver->getScreenSize();
833
834                 // Render scene
835                 driver->beginScene(true, true, video::SColor(0,0,0,0));
836                 driver->clearZBuffer();
837                 smgr->drawAll();
838
839                 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
840
841                 irr::video::IImage* rawImage =
842                                 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
843
844                 u8* pixels = static_cast<u8*>(rawImage->lock());
845                 if (!pixels)
846                 {
847                         rawImage->drop();
848                         return NULL;
849                 }
850
851                 core::rect<s32> source(
852                                 screen.Width /2 - (screen.Width  * (scaling / 2)),
853                                 screen.Height/2 - (screen.Height * (scaling / 2)),
854                                 screen.Width /2 + (screen.Width  * (scaling / 2)),
855                                 screen.Height/2 + (screen.Height * (scaling / 2))
856                         );
857
858                 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
859                                 partsize.Width, partsize.Height, GL_RGBA,
860                                 GL_UNSIGNED_BYTE, pixels);
861
862                 driver->endScene();
863
864                 // Drop scene manager
865                 smgr->drop();
866
867                 unsigned int pixelcount = partsize.Width*partsize.Height;
868
869                 u8* runptr = pixels;
870                 for (unsigned int i=0; i < pixelcount; i++) {
871
872                         u8 B = *runptr;
873                         u8 G = *(runptr+1);
874                         u8 R = *(runptr+2);
875                         u8 A = *(runptr+3);
876
877                         //BGRA -> RGBA
878                         *runptr = R;
879                         runptr ++;
880                         *runptr = G;
881                         runptr ++;
882                         *runptr = B;
883                         runptr ++;
884                         *runptr = A;
885                         runptr ++;
886                 }
887
888                 video::IImage* inventory_image =
889                                 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
890
891                 rawImage->copyToScaling(inventory_image);
892                 rawImage->drop();
893
894                 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
895
896                 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
897                 inventory_image->drop();
898
899                 if (rtt == NULL) {
900                         errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
901                         return NULL;
902                 }
903
904                 driver->makeColorKeyTexture(rtt, v2s32(0,0));
905
906                 if (params.delete_texture_on_shutdown)
907                         m_texture_trash.push_back(rtt);
908
909                 return rtt;
910         }
911 #endif
912
913         if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) {
914                 static bool warned = false;
915                 if (!warned)
916                 {
917                         errorstream<<"TextureSource::generateTextureFromMesh(): "
918                                 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
919                         warned = true;
920                 }
921                 return NULL;
922         }
923
924         // Create render target texture
925         video::ITexture *rtt = driver->addRenderTargetTexture(
926                         params.dim, params.rtt_texture_name.c_str(),
927                         video::ECF_A8R8G8B8);
928         if (rtt == NULL)
929         {
930                 errorstream<<"TextureSource::generateTextureFromMesh(): "
931                         <<"addRenderTargetTexture returned NULL."<<std::endl;
932                 return NULL;
933         }
934
935         // Set render target
936         if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
937                 driver->removeTexture(rtt);
938                 errorstream<<"TextureSource::generateTextureFromMesh(): "
939                         <<"failed to set render target"<<std::endl;
940                 return NULL;
941         }
942
943         // Get a scene manager
944         scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager();
945         assert(smgr_main);
946         scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
947         assert(smgr);
948
949         scene::IMeshSceneNode* meshnode =
950                         smgr->addMeshSceneNode(params.mesh, NULL,
951                                         -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
952         meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
953         meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
954         meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
955         meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
956         meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
957
958         scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
959                         params.camera_position, params.camera_lookat);
960         // second parameter of setProjectionMatrix (isOrthogonal) is ignored
961         camera->setProjectionMatrix(params.camera_projection_matrix, false);
962
963         smgr->setAmbientLight(params.ambient_light);
964         smgr->addLightSceneNode(0,
965                         params.light_position,
966                         params.light_color,
967                         params.light_radius);
968
969         // Render scene
970         driver->beginScene(true, true, video::SColor(0,0,0,0));
971         smgr->drawAll();
972         driver->endScene();
973
974         // Drop scene manager
975         smgr->drop();
976
977         // Unset render target
978         driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
979
980         if (params.delete_texture_on_shutdown)
981                 m_texture_trash.push_back(rtt);
982
983         return rtt;
984 }
985
986 video::IImage* TextureSource::generateImage(const std::string &name)
987 {
988         // Get the base image
989
990         const char separator = '^';
991         const char escape = '\\';
992         const char paren_open = '(';
993         const char paren_close = ')';
994
995         // Find last separator in the name
996         s32 last_separator_pos = -1;
997         u8 paren_bal = 0;
998         for (s32 i = name.size() - 1; i >= 0; i--) {
999                 if (i > 0 && name[i-1] == escape)
1000                         continue;
1001                 switch (name[i]) {
1002                 case separator:
1003                         if (paren_bal == 0) {
1004                                 last_separator_pos = i;
1005                                 i = -1; // break out of loop
1006                         }
1007                         break;
1008                 case paren_open:
1009                         if (paren_bal == 0) {
1010                                 errorstream << "generateImage(): unbalanced parentheses"
1011                                                 << "(extranous '(') while generating texture \""
1012                                                 << name << "\"" << std::endl;
1013                                 return NULL;
1014                         }
1015                         paren_bal--;
1016                         break;
1017                 case paren_close:
1018                         paren_bal++;
1019                         break;
1020                 default:
1021                         break;
1022                 }
1023         }
1024         if (paren_bal > 0) {
1025                 errorstream << "generateImage(): unbalanced parentheses"
1026                                 << "(missing matching '(') while generating texture \""
1027                                 << name << "\"" << std::endl;
1028                 return NULL;
1029         }
1030
1031
1032         video::IImage *baseimg = NULL;
1033
1034         /*
1035                 If separator was found, make the base image
1036                 using a recursive call.
1037         */
1038         if (last_separator_pos != -1) {
1039                 baseimg = generateImage(name.substr(0, last_separator_pos));
1040         }
1041
1042         /*
1043                 Parse out the last part of the name of the image and act
1044                 according to it
1045         */
1046
1047         std::string last_part_of_name = name.substr(last_separator_pos + 1);
1048
1049         /*
1050                 If this name is enclosed in parentheses, generate it
1051                 and blit it onto the base image
1052         */
1053         if (last_part_of_name[0] == paren_open
1054                         && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1055                 std::string name2 = last_part_of_name.substr(1,
1056                                 last_part_of_name.size() - 2);
1057                 video::IImage *tmp = generateImage(name2);
1058                 if (!tmp) {
1059                         errorstream << "generateImage(): "
1060                                 "Failed to generate \"" << name2 << "\""
1061                                 << std::endl;
1062                         return NULL;
1063                 }
1064                 core::dimension2d<u32> dim = tmp->getDimension();
1065                 if (baseimg) {
1066                         blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1067                         tmp->drop();
1068                 } else {
1069                         baseimg = tmp;
1070                 }
1071         } else if (!generateImagePart(last_part_of_name, baseimg)) {
1072                 // Generate image according to part of name
1073                 errorstream << "generateImage(): "
1074                                 "Failed to generate \"" << last_part_of_name << "\""
1075                                 << std::endl;
1076         }
1077
1078         // If no resulting image, print a warning
1079         if (baseimg == NULL) {
1080                 errorstream << "generateImage(): baseimg is NULL (attempted to"
1081                                 " create texture \"" << name << "\")" << std::endl;
1082         }
1083
1084         return baseimg;
1085 }
1086
1087 #ifdef __ANDROID__
1088 #include <GLES/gl.h>
1089 /**
1090  * Check and align image to npot2 if required by hardware
1091  * @param image image to check for npot2 alignment
1092  * @param driver driver to use for image operations
1093  * @return image or copy of image aligned to npot2
1094  */
1095
1096 inline u16 get_GL_major_version()
1097 {
1098         const GLubyte *gl_version = glGetString(GL_VERSION);
1099         return (u16) (gl_version[0] - '0');
1100 }
1101
1102 video::IImage * Align2Npot2(video::IImage * image,
1103                 video::IVideoDriver* driver)
1104 {
1105         if (image == NULL) {
1106                 return image;
1107         }
1108
1109         core::dimension2d<u32> dim = image->getDimension();
1110
1111         std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1112
1113         // Only GLES2 is trusted to correctly report npot support
1114         if (get_GL_major_version() > 1 &&
1115                         extensions.find("GL_OES_texture_npot") != std::string::npos) {
1116                 return image;
1117         }
1118
1119         unsigned int height = npot2(dim.Height);
1120         unsigned int width  = npot2(dim.Width);
1121
1122         if ((dim.Height == height) &&
1123                         (dim.Width == width)) {
1124                 return image;
1125         }
1126
1127         if (dim.Height > height) {
1128                 height *= 2;
1129         }
1130
1131         if (dim.Width > width) {
1132                 width *= 2;
1133         }
1134
1135         video::IImage *targetimage =
1136                         driver->createImage(video::ECF_A8R8G8B8,
1137                                         core::dimension2d<u32>(width, height));
1138
1139         if (targetimage != NULL) {
1140                 image->copyToScaling(targetimage);
1141         }
1142         image->drop();
1143         return targetimage;
1144 }
1145
1146 #endif
1147
1148 static std::string unescape_string(const std::string &str, const char esc = '\\')
1149 {
1150         std::string out;
1151         size_t pos = 0, cpos;
1152         out.reserve(str.size());
1153         while (1) {
1154                 cpos = str.find_first_of(esc, pos);
1155                 if (cpos == std::string::npos) {
1156                         out += str.substr(pos);
1157                         break;
1158                 }
1159                 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1160                 pos = cpos + 2;
1161         }
1162         return out;
1163 }
1164
1165 bool TextureSource::generateImagePart(std::string part_of_name,
1166                 video::IImage *& baseimg)
1167 {
1168         const char escape = '\\'; // same as in generateImage()
1169         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1170         sanity_check(driver);
1171
1172         // Stuff starting with [ are special commands
1173         if (part_of_name.empty() || part_of_name[0] != '[') {
1174                 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1175 #ifdef __ANDROID__
1176                 image = Align2Npot2(image, driver);
1177 #endif
1178                 if (image == NULL) {
1179                         if (!part_of_name.empty()) {
1180
1181                                 // Do not create normalmap dummies
1182                                 if (part_of_name.find("_normal.png") != std::string::npos) {
1183                                         warningstream << "generateImage(): Could not load normal map \""
1184                                                 << part_of_name << "\"" << std::endl;
1185                                         return true;
1186                                 }
1187
1188                                 errorstream << "generateImage(): Could not load image \""
1189                                         << part_of_name << "\" while building texture; "
1190                                         "Creating a dummy image" << std::endl;
1191                         }
1192
1193                         // Just create a dummy image
1194                         //core::dimension2d<u32> dim(2,2);
1195                         core::dimension2d<u32> dim(1,1);
1196                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
1197                         sanity_check(image != NULL);
1198                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
1199                         image->setPixel(1,0, video::SColor(255,0,255,0));
1200                         image->setPixel(0,1, video::SColor(255,0,0,255));
1201                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
1202                         image->setPixel(0,0, video::SColor(255,myrand()%256,
1203                                         myrand()%256,myrand()%256));
1204                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1205                                         myrand()%256,myrand()%256));
1206                         image->setPixel(0,1, video::SColor(255,myrand()%256,
1207                                         myrand()%256,myrand()%256));
1208                         image->setPixel(1,1, video::SColor(255,myrand()%256,
1209                                         myrand()%256,myrand()%256));*/
1210                 }
1211
1212                 // If base image is NULL, load as base.
1213                 if (baseimg == NULL)
1214                 {
1215                         //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1216                         /*
1217                                 Copy it this way to get an alpha channel.
1218                                 Otherwise images with alpha cannot be blitted on
1219                                 images that don't have alpha in the original file.
1220                         */
1221                         core::dimension2d<u32> dim = image->getDimension();
1222                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1223                         image->copyTo(baseimg);
1224                 }
1225                 // Else blit on base.
1226                 else
1227                 {
1228                         //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1229                         // Size of the copied area
1230                         core::dimension2d<u32> dim = image->getDimension();
1231                         //core::dimension2d<u32> dim(16,16);
1232                         // Position to copy the blitted to in the base image
1233                         core::position2d<s32> pos_to(0,0);
1234                         // Position to copy the blitted from in the blitted image
1235                         core::position2d<s32> pos_from(0,0);
1236                         // Blit
1237                         /*image->copyToWithAlpha(baseimg, pos_to,
1238                                         core::rect<s32>(pos_from, dim),
1239                                         video::SColor(255,255,255,255),
1240                                         NULL);*/
1241
1242                         core::dimension2d<u32> dim_dst = baseimg->getDimension();
1243                         if (dim == dim_dst) {
1244                                 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1245                         } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1246                                 // Upscale overlying image
1247                                 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1248                                         createImage(video::ECF_A8R8G8B8, dim_dst);
1249                                 image->copyToScaling(scaled_image);
1250
1251                                 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1252                                 scaled_image->drop();
1253                         } else {
1254                                 // Upscale base image
1255                                 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1256                                         createImage(video::ECF_A8R8G8B8, dim);
1257                                 baseimg->copyToScaling(scaled_base);
1258                                 baseimg->drop();
1259                                 baseimg = scaled_base;
1260
1261                                 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1262                         }
1263                 }
1264                 //cleanup
1265                 image->drop();
1266         }
1267         else
1268         {
1269                 // A special texture modification
1270
1271                 /*infostream<<"generateImage(): generating special "
1272                                 <<"modification \""<<part_of_name<<"\""
1273                                 <<std::endl;*/
1274
1275                 /*
1276                         [crack:N:P
1277                         [cracko:N:P
1278                         Adds a cracking texture
1279                         N = animation frame count, P = crack progression
1280                 */
1281                 if (str_starts_with(part_of_name, "[crack"))
1282                 {
1283                         if (baseimg == NULL) {
1284                                 errorstream<<"generateImagePart(): baseimg == NULL "
1285                                                 <<"for part_of_name=\""<<part_of_name
1286                                                 <<"\", cancelling."<<std::endl;
1287                                 return false;
1288                         }
1289
1290                         // Crack image number and overlay option
1291                         bool use_overlay = (part_of_name[6] == 'o');
1292                         Strfnd sf(part_of_name);
1293                         sf.next(":");
1294                         s32 frame_count = stoi(sf.next(":"));
1295                         s32 progression = stoi(sf.next(":"));
1296
1297                         if (progression >= 0) {
1298                                 /*
1299                                         Load crack image.
1300
1301                                         It is an image with a number of cracking stages
1302                                         horizontally tiled.
1303                                 */
1304                                 video::IImage *img_crack = m_sourcecache.getOrLoad(
1305                                         "crack_anylength.png");
1306
1307                                 if (img_crack) {
1308                                         draw_crack(img_crack, baseimg,
1309                                                 use_overlay, frame_count,
1310                                                 progression, driver);
1311                                         img_crack->drop();
1312                                 }
1313                         }
1314                 }
1315                 /*
1316                         [combine:WxH:X,Y=filename:X,Y=filename2
1317                         Creates a bigger texture from any amount of smaller ones
1318                 */
1319                 else if (str_starts_with(part_of_name, "[combine"))
1320                 {
1321                         Strfnd sf(part_of_name);
1322                         sf.next(":");
1323                         u32 w0 = stoi(sf.next("x"));
1324                         u32 h0 = stoi(sf.next(":"));
1325                         core::dimension2d<u32> dim(w0,h0);
1326                         if (baseimg == NULL) {
1327                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1328                                 baseimg->fill(video::SColor(0,0,0,0));
1329                         }
1330                         while (!sf.at_end()) {
1331                                 u32 x = stoi(sf.next(","));
1332                                 u32 y = stoi(sf.next("="));
1333                                 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1334                                 infostream<<"Adding \""<<filename
1335                                                 <<"\" to combined ("<<x<<","<<y<<")"
1336                                                 <<std::endl;
1337                                 video::IImage *img = generateImage(filename);
1338                                 if (img) {
1339                                         core::dimension2d<u32> dim = img->getDimension();
1340                                         infostream<<"Size "<<dim.Width
1341                                                         <<"x"<<dim.Height<<std::endl;
1342                                         core::position2d<s32> pos_base(x, y);
1343                                         video::IImage *img2 =
1344                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
1345                                         img->copyTo(img2);
1346                                         img->drop();
1347                                         /*img2->copyToWithAlpha(baseimg, pos_base,
1348                                                         core::rect<s32>(v2s32(0,0), dim),
1349                                                         video::SColor(255,255,255,255),
1350                                                         NULL);*/
1351                                         blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1352                                         img2->drop();
1353                                 } else {
1354                                         errorstream << "generateImagePart(): Failed to load image \""
1355                                                 << filename << "\" for [combine" << std::endl;
1356                                 }
1357                         }
1358                 }
1359                 /*
1360                         [brighten
1361                 */
1362                 else if (str_starts_with(part_of_name, "[brighten"))
1363                 {
1364                         if (baseimg == NULL) {
1365                                 errorstream<<"generateImagePart(): baseimg==NULL "
1366                                                 <<"for part_of_name=\""<<part_of_name
1367                                                 <<"\", cancelling."<<std::endl;
1368                                 return false;
1369                         }
1370
1371                         brighten(baseimg);
1372                 }
1373                 /*
1374                         [noalpha
1375                         Make image completely opaque.
1376                         Used for the leaves texture when in old leaves mode, so
1377                         that the transparent parts don't look completely black
1378                         when simple alpha channel is used for rendering.
1379                 */
1380                 else if (str_starts_with(part_of_name, "[noalpha"))
1381                 {
1382                         if (baseimg == NULL){
1383                                 errorstream<<"generateImagePart(): baseimg==NULL "
1384                                                 <<"for part_of_name=\""<<part_of_name
1385                                                 <<"\", cancelling."<<std::endl;
1386                                 return false;
1387                         }
1388
1389                         core::dimension2d<u32> dim = baseimg->getDimension();
1390
1391                         // Set alpha to full
1392                         for (u32 y=0; y<dim.Height; y++)
1393                         for (u32 x=0; x<dim.Width; x++)
1394                         {
1395                                 video::SColor c = baseimg->getPixel(x,y);
1396                                 c.setAlpha(255);
1397                                 baseimg->setPixel(x,y,c);
1398                         }
1399                 }
1400                 /*
1401                         [makealpha:R,G,B
1402                         Convert one color to transparent.
1403                 */
1404                 else if (str_starts_with(part_of_name, "[makealpha:"))
1405                 {
1406                         if (baseimg == NULL) {
1407                                 errorstream<<"generateImagePart(): baseimg == NULL "
1408                                                 <<"for part_of_name=\""<<part_of_name
1409                                                 <<"\", cancelling."<<std::endl;
1410                                 return false;
1411                         }
1412
1413                         Strfnd sf(part_of_name.substr(11));
1414                         u32 r1 = stoi(sf.next(","));
1415                         u32 g1 = stoi(sf.next(","));
1416                         u32 b1 = stoi(sf.next(""));
1417
1418                         core::dimension2d<u32> dim = baseimg->getDimension();
1419
1420                         /*video::IImage *oldbaseimg = baseimg;
1421                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1422                         oldbaseimg->copyTo(baseimg);
1423                         oldbaseimg->drop();*/
1424
1425                         // Set alpha to full
1426                         for (u32 y=0; y<dim.Height; y++)
1427                         for (u32 x=0; x<dim.Width; x++)
1428                         {
1429                                 video::SColor c = baseimg->getPixel(x,y);
1430                                 u32 r = c.getRed();
1431                                 u32 g = c.getGreen();
1432                                 u32 b = c.getBlue();
1433                                 if (!(r == r1 && g == g1 && b == b1))
1434                                         continue;
1435                                 c.setAlpha(0);
1436                                 baseimg->setPixel(x,y,c);
1437                         }
1438                 }
1439                 /*
1440                         [transformN
1441                         Rotates and/or flips the image.
1442
1443                         N can be a number (between 0 and 7) or a transform name.
1444                         Rotations are counter-clockwise.
1445                         0  I      identity
1446                         1  R90    rotate by 90 degrees
1447                         2  R180   rotate by 180 degrees
1448                         3  R270   rotate by 270 degrees
1449                         4  FX     flip X
1450                         5  FXR90  flip X then rotate by 90 degrees
1451                         6  FY     flip Y
1452                         7  FYR90  flip Y then rotate by 90 degrees
1453
1454                         Note: Transform names can be concatenated to produce
1455                         their product (applies the first then the second).
1456                         The resulting transform will be equivalent to one of the
1457                         eight existing ones, though (see: dihedral group).
1458                 */
1459                 else if (str_starts_with(part_of_name, "[transform"))
1460                 {
1461                         if (baseimg == NULL) {
1462                                 errorstream<<"generateImagePart(): baseimg == NULL "
1463                                                 <<"for part_of_name=\""<<part_of_name
1464                                                 <<"\", cancelling."<<std::endl;
1465                                 return false;
1466                         }
1467
1468                         u32 transform = parseImageTransform(part_of_name.substr(10));
1469                         core::dimension2d<u32> dim = imageTransformDimension(
1470                                         transform, baseimg->getDimension());
1471                         video::IImage *image = driver->createImage(
1472                                         baseimg->getColorFormat(), dim);
1473                         sanity_check(image != NULL);
1474                         imageTransform(transform, baseimg, image);
1475                         baseimg->drop();
1476                         baseimg = image;
1477                 }
1478                 /*
1479                         [inventorycube{topimage{leftimage{rightimage
1480                         In every subimage, replace ^ with &.
1481                         Create an "inventory cube".
1482                         NOTE: This should be used only on its own.
1483                         Example (a grass block (not actually used in game):
1484                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1485                 */
1486                 else if (str_starts_with(part_of_name, "[inventorycube"))
1487                 {
1488                         if (baseimg != NULL){
1489                                 errorstream<<"generateImagePart(): baseimg != NULL "
1490                                                 <<"for part_of_name=\""<<part_of_name
1491                                                 <<"\", cancelling."<<std::endl;
1492                                 return false;
1493                         }
1494
1495                         str_replace(part_of_name, '&', '^');
1496                         Strfnd sf(part_of_name);
1497                         sf.next("{");
1498                         std::string imagename_top = sf.next("{");
1499                         std::string imagename_left = sf.next("{");
1500                         std::string imagename_right = sf.next("{");
1501
1502                         // Generate images for the faces of the cube
1503                         video::IImage *img_top = generateImage(imagename_top);
1504                         video::IImage *img_left = generateImage(imagename_left);
1505                         video::IImage *img_right = generateImage(imagename_right);
1506
1507                         if (img_top == NULL || img_left == NULL || img_right == NULL) {
1508                                 errorstream << "generateImagePart(): Failed to create textures"
1509                                                 << " for inventorycube \"" << part_of_name << "\""
1510                                                 << std::endl;
1511                                 baseimg = generateImage(imagename_top);
1512                                 return true;
1513                         }
1514
1515 #ifdef __ANDROID__
1516                         assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1517                         assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1518
1519                         assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1520                         assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1521
1522                         assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1523                         assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1524 #endif
1525
1526                         // Create textures from images
1527                         video::ITexture *texture_top = driver->addTexture(
1528                                         (imagename_top + "__temp__").c_str(), img_top);
1529                         video::ITexture *texture_left = driver->addTexture(
1530                                         (imagename_left + "__temp__").c_str(), img_left);
1531                         video::ITexture *texture_right = driver->addTexture(
1532                                         (imagename_right + "__temp__").c_str(), img_right);
1533                         FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1534
1535                         // Drop images
1536                         img_top->drop();
1537                         img_left->drop();
1538                         img_right->drop();
1539
1540                         /*
1541                                 Draw a cube mesh into a render target texture
1542                         */
1543                         scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1544                         setMeshColor(cube, video::SColor(255, 255, 255, 255));
1545                         cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1546                         cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1547                         cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1548                         cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1549                         cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1550                         cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1551
1552                         TextureFromMeshParams params;
1553                         params.mesh = cube;
1554                         params.dim.set(64, 64);
1555                         params.rtt_texture_name = part_of_name + "_RTT";
1556                         // We will delete the rtt texture ourselves
1557                         params.delete_texture_on_shutdown = false;
1558                         params.camera_position.set(0, 1.0, -1.5);
1559                         params.camera_position.rotateXZBy(45);
1560                         params.camera_lookat.set(0, 0, 0);
1561                         // Set orthogonal projection
1562                         params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1563                                         1.65, 1.65, 0, 100);
1564
1565                         params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1566                         params.light_position.set(10, 100, -50);
1567                         params.light_color.set(1.0, 0.5, 0.5, 0.5);
1568                         params.light_radius = 1000;
1569
1570                         video::ITexture *rtt = generateTextureFromMesh(params);
1571
1572                         // Drop mesh
1573                         cube->drop();
1574
1575                         // Free textures
1576                         driver->removeTexture(texture_top);
1577                         driver->removeTexture(texture_left);
1578                         driver->removeTexture(texture_right);
1579
1580                         if (rtt == NULL) {
1581                                 baseimg = generateImage(imagename_top);
1582                                 return true;
1583                         }
1584
1585                         // Create image of render target
1586                         video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1587                         FATAL_ERROR_IF(!image, "Could not create image of render target");
1588
1589                         // Cleanup texture
1590                         driver->removeTexture(rtt);
1591
1592                         baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1593
1594                         if (image) {
1595                                 image->copyTo(baseimg);
1596                                 image->drop();
1597                         }
1598                 }
1599                 /*
1600                         [lowpart:percent:filename
1601                         Adds the lower part of a texture
1602                 */
1603                 else if (str_starts_with(part_of_name, "[lowpart:"))
1604                 {
1605                         Strfnd sf(part_of_name);
1606                         sf.next(":");
1607                         u32 percent = stoi(sf.next(":"));
1608                         std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1609
1610                         if (baseimg == NULL)
1611                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1612                         video::IImage *img = generateImage(filename);
1613                         if (img)
1614                         {
1615                                 core::dimension2d<u32> dim = img->getDimension();
1616                                 core::position2d<s32> pos_base(0, 0);
1617                                 video::IImage *img2 =
1618                                                 driver->createImage(video::ECF_A8R8G8B8, dim);
1619                                 img->copyTo(img2);
1620                                 img->drop();
1621                                 core::position2d<s32> clippos(0, 0);
1622                                 clippos.Y = dim.Height * (100-percent) / 100;
1623                                 core::dimension2d<u32> clipdim = dim;
1624                                 clipdim.Height = clipdim.Height * percent / 100 + 1;
1625                                 core::rect<s32> cliprect(clippos, clipdim);
1626                                 img2->copyToWithAlpha(baseimg, pos_base,
1627                                                 core::rect<s32>(v2s32(0,0), dim),
1628                                                 video::SColor(255,255,255,255),
1629                                                 &cliprect);
1630                                 img2->drop();
1631                         }
1632                 }
1633                 /*
1634                         [verticalframe:N:I
1635                         Crops a frame of a vertical animation.
1636                         N = frame count, I = frame index
1637                 */
1638                 else if (str_starts_with(part_of_name, "[verticalframe:"))
1639                 {
1640                         Strfnd sf(part_of_name);
1641                         sf.next(":");
1642                         u32 frame_count = stoi(sf.next(":"));
1643                         u32 frame_index = stoi(sf.next(":"));
1644
1645                         if (baseimg == NULL){
1646                                 errorstream<<"generateImagePart(): baseimg != NULL "
1647                                                 <<"for part_of_name=\""<<part_of_name
1648                                                 <<"\", cancelling."<<std::endl;
1649                                 return false;
1650                         }
1651
1652                         v2u32 frame_size = baseimg->getDimension();
1653                         frame_size.Y /= frame_count;
1654
1655                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1656                                         frame_size);
1657                         if (!img){
1658                                 errorstream<<"generateImagePart(): Could not create image "
1659                                                 <<"for part_of_name=\""<<part_of_name
1660                                                 <<"\", cancelling."<<std::endl;
1661                                 return false;
1662                         }
1663
1664                         // Fill target image with transparency
1665                         img->fill(video::SColor(0,0,0,0));
1666
1667                         core::dimension2d<u32> dim = frame_size;
1668                         core::position2d<s32> pos_dst(0, 0);
1669                         core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1670                         baseimg->copyToWithAlpha(img, pos_dst,
1671                                         core::rect<s32>(pos_src, dim),
1672                                         video::SColor(255,255,255,255),
1673                                         NULL);
1674                         // Replace baseimg
1675                         baseimg->drop();
1676                         baseimg = img;
1677                 }
1678                 /*
1679                         [mask:filename
1680                         Applies a mask to an image
1681                 */
1682                 else if (str_starts_with(part_of_name, "[mask:"))
1683                 {
1684                         if (baseimg == NULL) {
1685                                 errorstream << "generateImage(): baseimg == NULL "
1686                                                 << "for part_of_name=\"" << part_of_name
1687                                                 << "\", cancelling." << std::endl;
1688                                 return false;
1689                         }
1690                         Strfnd sf(part_of_name);
1691                         sf.next(":");
1692                         std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1693
1694                         video::IImage *img = generateImage(filename);
1695                         if (img) {
1696                                 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1697                                                 img->getDimension());
1698                                 img->drop();
1699                         } else {
1700                                 errorstream << "generateImage(): Failed to load \""
1701                                                 << filename << "\".";
1702                         }
1703                 }
1704                 /*
1705                 [multiply:color
1706                         multiplys a given color to any pixel of an image
1707                         color = color as ColorString
1708                 */
1709                 else if (str_starts_with(part_of_name, "[multiply:")) {
1710                         Strfnd sf(part_of_name);
1711                         sf.next(":");
1712                         std::string color_str = sf.next(":");
1713
1714                         if (baseimg == NULL) {
1715                                 errorstream << "generateImagePart(): baseimg != NULL "
1716                                                 << "for part_of_name=\"" << part_of_name
1717                                                 << "\", cancelling." << std::endl;
1718                                 return false;
1719                         }
1720
1721                         video::SColor color;
1722
1723                         if (!parseColorString(color_str, color, false))
1724                                 return false;
1725
1726                         apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1727                 }
1728                 /*
1729                         [colorize:color
1730                         Overlays image with given color
1731                         color = color as ColorString
1732                 */
1733                 else if (str_starts_with(part_of_name, "[colorize:"))
1734                 {
1735                         Strfnd sf(part_of_name);
1736                         sf.next(":");
1737                         std::string color_str = sf.next(":");
1738                         std::string ratio_str = sf.next(":");
1739
1740                         if (baseimg == NULL) {
1741                                 errorstream << "generateImagePart(): baseimg != NULL "
1742                                                 << "for part_of_name=\"" << part_of_name
1743                                                 << "\", cancelling." << std::endl;
1744                                 return false;
1745                         }
1746
1747                         video::SColor color;
1748                         int ratio = -1;
1749                         bool keep_alpha = false;
1750
1751                         if (!parseColorString(color_str, color, false))
1752                                 return false;
1753
1754                         if (is_number(ratio_str))
1755                                 ratio = mystoi(ratio_str, 0, 255);
1756                         else if (ratio_str == "alpha")
1757                                 keep_alpha = true;
1758
1759                         apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1760                 }
1761                 /*
1762                         [applyfiltersformesh
1763                         Internal modifier
1764                 */
1765                 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1766                 {
1767                         // Apply the "clean transparent" filter, if configured.
1768                         if (g_settings->getBool("texture_clean_transparent"))
1769                                 imageCleanTransparent(baseimg, 127);
1770
1771                         /* Upscale textures to user's requested minimum size.  This is a trick to make
1772                          * filters look as good on low-res textures as on high-res ones, by making
1773                          * low-res textures BECOME high-res ones.  This is helpful for worlds that
1774                          * mix high- and low-res textures, or for mods with least-common-denominator
1775                          * textures that don't have the resources to offer high-res alternatives.
1776                          */
1777                         s32 scaleto = g_settings->getS32("texture_min_size");
1778                         if (scaleto > 1) {
1779                                 const core::dimension2d<u32> dim = baseimg->getDimension();
1780
1781                                 /* Calculate scaling needed to make the shortest texture dimension
1782                                  * equal to the target minimum.  If e.g. this is a vertical frames
1783                                  * animation, the short dimension will be the real size.
1784                                  */
1785                                 if ((dim.Width == 0) || (dim.Height == 0)) {
1786                                         errorstream << "generateImagePart(): Illegal 0 dimension "
1787                                                 << "for part_of_name=\""<< part_of_name
1788                                                 << "\", cancelling." << std::endl;
1789                                         return false;
1790                                 }
1791                                 u32 xscale = scaleto / dim.Width;
1792                                 u32 yscale = scaleto / dim.Height;
1793                                 u32 scale = (xscale > yscale) ? xscale : yscale;
1794
1795                                 // Never downscale; only scale up by 2x or more.
1796                                 if (scale > 1) {
1797                                         u32 w = scale * dim.Width;
1798                                         u32 h = scale * dim.Height;
1799                                         const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1800                                         video::IImage *newimg = driver->createImage(
1801                                                         baseimg->getColorFormat(), newdim);
1802                                         baseimg->copyToScaling(newimg);
1803                                         baseimg->drop();
1804                                         baseimg = newimg;
1805                                 }
1806                         }
1807                 }
1808                 /*
1809                         [resize:WxH
1810                         Resizes the base image to the given dimensions
1811                 */
1812                 else if (str_starts_with(part_of_name, "[resize"))
1813                 {
1814                         if (baseimg == NULL) {
1815                                 errorstream << "generateImagePart(): baseimg == NULL "
1816                                                 << "for part_of_name=\""<< part_of_name
1817                                                 << "\", cancelling." << std::endl;
1818                                 return false;
1819                         }
1820
1821                         Strfnd sf(part_of_name);
1822                         sf.next(":");
1823                         u32 width = stoi(sf.next("x"));
1824                         u32 height = stoi(sf.next(""));
1825                         core::dimension2d<u32> dim(width, height);
1826
1827                         video::IImage *image = RenderingEngine::get_video_driver()->
1828                                 createImage(video::ECF_A8R8G8B8, dim);
1829                         baseimg->copyToScaling(image);
1830                         baseimg->drop();
1831                         baseimg = image;
1832                 }
1833                 /*
1834                         [opacity:R
1835                         Makes the base image transparent according to the given ratio.
1836                         R must be between 0 and 255.
1837                         0 means totally transparent.
1838                         255 means totally opaque.
1839                 */
1840                 else if (str_starts_with(part_of_name, "[opacity:")) {
1841                         if (baseimg == NULL) {
1842                                 errorstream << "generateImagePart(): baseimg == NULL "
1843                                                 << "for part_of_name=\"" << part_of_name
1844                                                 << "\", cancelling." << std::endl;
1845                                 return false;
1846                         }
1847
1848                         Strfnd sf(part_of_name);
1849                         sf.next(":");
1850
1851                         u32 ratio = mystoi(sf.next(""), 0, 255);
1852
1853                         core::dimension2d<u32> dim = baseimg->getDimension();
1854
1855                         for (u32 y = 0; y < dim.Height; y++)
1856                         for (u32 x = 0; x < dim.Width; x++)
1857                         {
1858                                 video::SColor c = baseimg->getPixel(x, y);
1859                                 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1860                                 baseimg->setPixel(x, y, c);
1861                         }
1862                 }
1863                 /*
1864                         [invert:mode
1865                         Inverts the given channels of the base image.
1866                         Mode may contain the characters "r", "g", "b", "a".
1867                         Only the channels that are mentioned in the mode string
1868                         will be inverted.
1869                 */
1870                 else if (str_starts_with(part_of_name, "[invert:")) {
1871                         if (baseimg == NULL) {
1872                                 errorstream << "generateImagePart(): baseimg == NULL "
1873                                                 << "for part_of_name=\"" << part_of_name
1874                                                 << "\", cancelling." << std::endl;
1875                                 return false;
1876                         }
1877
1878                         Strfnd sf(part_of_name);
1879                         sf.next(":");
1880
1881                         std::string mode = sf.next("");
1882                         u32 mask = 0;
1883                         if (mode.find('a') != std::string::npos)
1884                                 mask |= 0xff000000UL;
1885                         if (mode.find('r') != std::string::npos)
1886                                 mask |= 0x00ff0000UL;
1887                         if (mode.find('g') != std::string::npos)
1888                                 mask |= 0x0000ff00UL;
1889                         if (mode.find('b') != std::string::npos)
1890                                 mask |= 0x000000ffUL;
1891
1892                         core::dimension2d<u32> dim = baseimg->getDimension();
1893
1894                         for (u32 y = 0; y < dim.Height; y++)
1895                         for (u32 x = 0; x < dim.Width; x++)
1896                         {
1897                                 video::SColor c = baseimg->getPixel(x, y);
1898                                 c.color ^= mask;
1899                                 baseimg->setPixel(x, y, c);
1900                         }
1901                 }
1902                 /*
1903                         [sheet:WxH:X,Y
1904                         Retrieves a tile at position X,Y (in tiles)
1905                         from the base image it assumes to be a
1906                         tilesheet with dimensions W,H (in tiles).
1907                 */
1908                 else if (part_of_name.substr(0,7) == "[sheet:") {
1909                         if (baseimg == NULL) {
1910                                 errorstream << "generateImagePart(): baseimg != NULL "
1911                                                 << "for part_of_name=\"" << part_of_name
1912                                                 << "\", cancelling." << std::endl;
1913                                 return false;
1914                         }
1915
1916                         Strfnd sf(part_of_name);
1917                         sf.next(":");
1918                         u32 w0 = stoi(sf.next("x"));
1919                         u32 h0 = stoi(sf.next(":"));
1920                         u32 x0 = stoi(sf.next(","));
1921                         u32 y0 = stoi(sf.next(":"));
1922
1923                         core::dimension2d<u32> img_dim = baseimg->getDimension();
1924                         core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1925
1926                         video::IImage *img = driver->createImage(
1927                                         video::ECF_A8R8G8B8, tile_dim);
1928                         if (!img) {
1929                                 errorstream << "generateImagePart(): Could not create image "
1930                                                 << "for part_of_name=\"" << part_of_name
1931                                                 << "\", cancelling." << std::endl;
1932                                 return false;
1933                         }
1934
1935                         img->fill(video::SColor(0,0,0,0));
1936                         v2u32 vdim(tile_dim);
1937                         core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1938                         baseimg->copyToWithAlpha(img, v2s32(0), rect,
1939                                         video::SColor(255,255,255,255), NULL);
1940
1941                         // Replace baseimg
1942                         baseimg->drop();
1943                         baseimg = img;
1944                 }
1945                 else
1946                 {
1947                         errorstream << "generateImagePart(): Invalid "
1948                                         " modification: \"" << part_of_name << "\"" << std::endl;
1949                 }
1950         }
1951
1952         return true;
1953 }
1954
1955 /*
1956         Draw an image on top of an another one, using the alpha channel of the
1957         source image
1958
1959         This exists because IImage::copyToWithAlpha() doesn't seem to always
1960         work.
1961 */
1962 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1963                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1964 {
1965         for (u32 y0=0; y0<size.Y; y0++)
1966         for (u32 x0=0; x0<size.X; x0++)
1967         {
1968                 s32 src_x = src_pos.X + x0;
1969                 s32 src_y = src_pos.Y + y0;
1970                 s32 dst_x = dst_pos.X + x0;
1971                 s32 dst_y = dst_pos.Y + y0;
1972                 video::SColor src_c = src->getPixel(src_x, src_y);
1973                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1974                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1975                 dst->setPixel(dst_x, dst_y, dst_c);
1976         }
1977 }
1978
1979 /*
1980         Draw an image on top of an another one, using the alpha channel of the
1981         source image; only modify fully opaque pixels in destinaion
1982 */
1983 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1984                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1985 {
1986         for (u32 y0=0; y0<size.Y; y0++)
1987         for (u32 x0=0; x0<size.X; x0++)
1988         {
1989                 s32 src_x = src_pos.X + x0;
1990                 s32 src_y = src_pos.Y + y0;
1991                 s32 dst_x = dst_pos.X + x0;
1992                 s32 dst_y = dst_pos.Y + y0;
1993                 video::SColor src_c = src->getPixel(src_x, src_y);
1994                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1995                 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1996                 {
1997                         dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1998                         dst->setPixel(dst_x, dst_y, dst_c);
1999                 }
2000         }
2001 }
2002
2003 // This function has been disabled because it is currently unused.
2004 // Feel free to re-enable if you find it handy.
2005 #if 0
2006 /*
2007         Draw an image on top of an another one, using the specified ratio
2008         modify all partially-opaque pixels in the destination.
2009 */
2010 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2011                 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2012 {
2013         for (u32 y0 = 0; y0 < size.Y; y0++)
2014         for (u32 x0 = 0; x0 < size.X; x0++)
2015         {
2016                 s32 src_x = src_pos.X + x0;
2017                 s32 src_y = src_pos.Y + y0;
2018                 s32 dst_x = dst_pos.X + x0;
2019                 s32 dst_y = dst_pos.Y + y0;
2020                 video::SColor src_c = src->getPixel(src_x, src_y);
2021                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2022                 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2023                 {
2024                         if (ratio == -1)
2025                                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2026                         else
2027                                 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2028                         dst->setPixel(dst_x, dst_y, dst_c);
2029                 }
2030         }
2031 }
2032 #endif
2033
2034 /*
2035         Apply color to destination
2036 */
2037 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2038                 const video::SColor &color, int ratio, bool keep_alpha)
2039 {
2040         u32 alpha = color.getAlpha();
2041         video::SColor dst_c;
2042         if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2043                 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2044                         dst_c = color;
2045                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2046                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2047                                 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2048                                 if (dst_alpha > 0) {
2049                                         dst_c.setAlpha(dst_alpha * alpha / 255);
2050                                         dst->setPixel(x, y, dst_c);
2051                                 }
2052                         }
2053                 } else { // replace the color including the alpha
2054                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2055                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2056                                 if (dst->getPixel(x, y).getAlpha() > 0)
2057                                         dst->setPixel(x, y, color);
2058                 }
2059         } else {  // interpolate between the color and destination
2060                 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2061                 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2062                 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2063                         dst_c = dst->getPixel(x, y);
2064                         if (dst_c.getAlpha() > 0) {
2065                                 dst_c = color.getInterpolated(dst_c, interp);
2066                                 dst->setPixel(x, y, dst_c);
2067                         }
2068                 }
2069         }
2070 }
2071
2072 /*
2073         Apply color to destination
2074 */
2075 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2076                 const video::SColor &color)
2077 {
2078         video::SColor dst_c;
2079
2080         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2081         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2082                 dst_c = dst->getPixel(x, y);
2083                 dst_c.set(
2084                                 dst_c.getAlpha(),
2085                                 (dst_c.getRed() * color.getRed()) / 255,
2086                                 (dst_c.getGreen() * color.getGreen()) / 255,
2087                                 (dst_c.getBlue() * color.getBlue()) / 255
2088                                 );
2089                 dst->setPixel(x, y, dst_c);
2090         }
2091 }
2092
2093 /*
2094         Apply mask to destination
2095 */
2096 static void apply_mask(video::IImage *mask, video::IImage *dst,
2097                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2098 {
2099         for (u32 y0 = 0; y0 < size.Y; y0++) {
2100                 for (u32 x0 = 0; x0 < size.X; x0++) {
2101                         s32 mask_x = x0 + mask_pos.X;
2102                         s32 mask_y = y0 + mask_pos.Y;
2103                         s32 dst_x = x0 + dst_pos.X;
2104                         s32 dst_y = y0 + dst_pos.Y;
2105                         video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2106                         video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2107                         dst_c.color &= mask_c.color;
2108                         dst->setPixel(dst_x, dst_y, dst_c);
2109                 }
2110         }
2111 }
2112
2113 static void draw_crack(video::IImage *crack, video::IImage *dst,
2114                 bool use_overlay, s32 frame_count, s32 progression,
2115                 video::IVideoDriver *driver)
2116 {
2117         // Dimension of destination image
2118         core::dimension2d<u32> dim_dst = dst->getDimension();
2119         // Dimension of original image
2120         core::dimension2d<u32> dim_crack = crack->getDimension();
2121         // Count of crack stages
2122         s32 crack_count = dim_crack.Height / dim_crack.Width;
2123         // Limit frame_count
2124         if (frame_count > (s32) dim_dst.Height)
2125                 frame_count = dim_dst.Height;
2126         if (frame_count < 1)
2127                 frame_count = 1;
2128         // Limit progression
2129         if (progression > crack_count-1)
2130                 progression = crack_count-1;
2131         // Dimension of a single crack stage
2132         core::dimension2d<u32> dim_crack_cropped(
2133                 dim_crack.Width,
2134                 dim_crack.Width
2135         );
2136         // Dimension of the scaled crack stage,
2137         // which is the same as the dimension of a single destination frame
2138         core::dimension2d<u32> dim_crack_scaled(
2139                 dim_dst.Width,
2140                 dim_dst.Height / frame_count
2141         );
2142         // Create cropped and scaled crack images
2143         video::IImage *crack_cropped = driver->createImage(
2144                         video::ECF_A8R8G8B8, dim_crack_cropped);
2145         video::IImage *crack_scaled = driver->createImage(
2146                         video::ECF_A8R8G8B8, dim_crack_scaled);
2147
2148         if (crack_cropped && crack_scaled)
2149         {
2150                 // Crop crack image
2151                 v2s32 pos_crack(0, progression*dim_crack.Width);
2152                 crack->copyTo(crack_cropped,
2153                                 v2s32(0,0),
2154                                 core::rect<s32>(pos_crack, dim_crack_cropped));
2155                 // Scale crack image by copying
2156                 crack_cropped->copyToScaling(crack_scaled);
2157                 // Copy or overlay crack image onto each frame
2158                 for (s32 i = 0; i < frame_count; ++i)
2159                 {
2160                         v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2161                         if (use_overlay)
2162                         {
2163                                 blit_with_alpha_overlay(crack_scaled, dst,
2164                                                 v2s32(0,0), dst_pos,
2165                                                 dim_crack_scaled);
2166                         }
2167                         else
2168                         {
2169                                 blit_with_alpha(crack_scaled, dst,
2170                                                 v2s32(0,0), dst_pos,
2171                                                 dim_crack_scaled);
2172                         }
2173                 }
2174         }
2175
2176         if (crack_scaled)
2177                 crack_scaled->drop();
2178
2179         if (crack_cropped)
2180                 crack_cropped->drop();
2181 }
2182
2183 void brighten(video::IImage *image)
2184 {
2185         if (image == NULL)
2186                 return;
2187
2188         core::dimension2d<u32> dim = image->getDimension();
2189
2190         for (u32 y=0; y<dim.Height; y++)
2191         for (u32 x=0; x<dim.Width; x++)
2192         {
2193                 video::SColor c = image->getPixel(x,y);
2194                 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2195                 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2196                 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2197                 image->setPixel(x,y,c);
2198         }
2199 }
2200
2201 u32 parseImageTransform(const std::string& s)
2202 {
2203         int total_transform = 0;
2204
2205         std::string transform_names[8];
2206         transform_names[0] = "i";
2207         transform_names[1] = "r90";
2208         transform_names[2] = "r180";
2209         transform_names[3] = "r270";
2210         transform_names[4] = "fx";
2211         transform_names[6] = "fy";
2212
2213         std::size_t pos = 0;
2214         while(pos < s.size())
2215         {
2216                 int transform = -1;
2217                 for (int i = 0; i <= 7; ++i)
2218                 {
2219                         const std::string &name_i = transform_names[i];
2220
2221                         if (s[pos] == ('0' + i))
2222                         {
2223                                 transform = i;
2224                                 pos++;
2225                                 break;
2226                         }
2227
2228                         if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2229                                 transform = i;
2230                                 pos += name_i.size();
2231                                 break;
2232                         }
2233                 }
2234                 if (transform < 0)
2235                         break;
2236
2237                 // Multiply total_transform and transform in the group D4
2238                 int new_total = 0;
2239                 if (transform < 4)
2240                         new_total = (transform + total_transform) % 4;
2241                 else
2242                         new_total = (transform - total_transform + 8) % 4;
2243                 if ((transform >= 4) ^ (total_transform >= 4))
2244                         new_total += 4;
2245
2246                 total_transform = new_total;
2247         }
2248         return total_transform;
2249 }
2250
2251 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2252 {
2253         if (transform % 2 == 0)
2254                 return dim;
2255
2256         return core::dimension2d<u32>(dim.Height, dim.Width);
2257 }
2258
2259 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2260 {
2261         if (src == NULL || dst == NULL)
2262                 return;
2263
2264         core::dimension2d<u32> dstdim = dst->getDimension();
2265
2266         // Pre-conditions
2267         assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2268         assert(transform <= 7);
2269
2270         /*
2271                 Compute the transformation from source coordinates (sx,sy)
2272                 to destination coordinates (dx,dy).
2273         */
2274         int sxn = 0;
2275         int syn = 2;
2276         if (transform == 0)         // identity
2277                 sxn = 0, syn = 2;  //   sx = dx, sy = dy
2278         else if (transform == 1)    // rotate by 90 degrees ccw
2279                 sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
2280         else if (transform == 2)    // rotate by 180 degrees
2281                 sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
2282         else if (transform == 3)    // rotate by 270 degrees ccw
2283                 sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
2284         else if (transform == 4)    // flip x
2285                 sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
2286         else if (transform == 5)    // flip x then rotate by 90 degrees ccw
2287                 sxn = 2, syn = 0;  //   sx = dy, sy = dx
2288         else if (transform == 6)    // flip y
2289                 sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
2290         else if (transform == 7)    // flip y then rotate by 90 degrees ccw
2291                 sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
2292
2293         for (u32 dy=0; dy<dstdim.Height; dy++)
2294         for (u32 dx=0; dx<dstdim.Width; dx++)
2295         {
2296                 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2297                 u32 sx = entries[sxn];
2298                 u32 sy = entries[syn];
2299                 video::SColor c = src->getPixel(sx,sy);
2300                 dst->setPixel(dx,dy,c);
2301         }
2302 }
2303
2304 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2305 {
2306         if (isKnownSourceImage("override_normal.png"))
2307                 return getTexture("override_normal.png");
2308         std::string fname_base = name;
2309         static const char *normal_ext = "_normal.png";
2310         static const u32 normal_ext_size = strlen(normal_ext);
2311         size_t pos = fname_base.find('.');
2312         std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2313         if (isKnownSourceImage(fname_normal)) {
2314                 // look for image extension and replace it
2315                 size_t i = 0;
2316                 while ((i = fname_base.find('.', i)) != std::string::npos) {
2317                         fname_base.replace(i, 4, normal_ext);
2318                         i += normal_ext_size;
2319                 }
2320                 return getTexture(fname_base);
2321         }
2322         return NULL;
2323 }
2324
2325 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2326 {
2327         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2328         video::SColor c(0, 0, 0, 0);
2329         video::ITexture *texture = getTexture(name);
2330         video::IImage *image = driver->createImage(texture,
2331                 core::position2d<s32>(0, 0),
2332                 texture->getOriginalSize());
2333         u32 total = 0;
2334         u32 tR = 0;
2335         u32 tG = 0;
2336         u32 tB = 0;
2337         core::dimension2d<u32> dim = image->getDimension();
2338         u16 step = 1;
2339         if (dim.Width > 16)
2340                 step = dim.Width / 16;
2341         for (u16 x = 0; x < dim.Width; x += step) {
2342                 for (u16 y = 0; y < dim.Width; y += step) {
2343                         c = image->getPixel(x,y);
2344                         if (c.getAlpha() > 0) {
2345                                 total++;
2346                                 tR += c.getRed();
2347                                 tG += c.getGreen();
2348                                 tB += c.getBlue();
2349                         }
2350                 }
2351         }
2352         image->drop();
2353         if (total > 0) {
2354                 c.setRed(tR / total);
2355                 c.setGreen(tG / total);
2356                 c.setBlue(tB / total);
2357         }
2358         c.setAlpha(255);
2359         return c;
2360 }
2361
2362
2363 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2364 {
2365         std::string tname = "__shaderFlagsTexture";
2366         tname += normalmap_present ? "1" : "0";
2367
2368         if (isKnownSourceImage(tname)) {
2369                 return getTexture(tname);
2370         }
2371
2372         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2373         video::IImage *flags_image = driver->createImage(
2374                 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2375         sanity_check(flags_image != NULL);
2376         video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2377         flags_image->setPixel(0, 0, c);
2378         insertSourceImage(tname, flags_image);
2379         flags_image->drop();
2380         return getTexture(tname);
2381
2382 }