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