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