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