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