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