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