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