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