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