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