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