]> git.lizzy.rs Git - minetest.git/blob - src/client/tile.cpp
Network cleanup (#6302)
[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);
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                         bool use_overlay = (part_of_name[6] == 'o');
1284                         Strfnd sf(part_of_name);
1285                         sf.next(":");
1286                         s32 frame_count = stoi(sf.next(":"));
1287                         s32 progression = stoi(sf.next(":"));
1288
1289                         if (progression >= 0) {
1290                                 /*
1291                                         Load crack image.
1292
1293                                         It is an image with a number of cracking stages
1294                                         horizontally tiled.
1295                                 */
1296                                 video::IImage *img_crack = m_sourcecache.getOrLoad(
1297                                         "crack_anylength.png");
1298
1299                                 if (img_crack) {
1300                                         draw_crack(img_crack, baseimg,
1301                                                 use_overlay, frame_count,
1302                                                 progression, driver);
1303                                         img_crack->drop();
1304                                 }
1305                         }
1306                 }
1307                 /*
1308                         [combine:WxH:X,Y=filename:X,Y=filename2
1309                         Creates a bigger texture from any amount of smaller ones
1310                 */
1311                 else if (str_starts_with(part_of_name, "[combine"))
1312                 {
1313                         Strfnd sf(part_of_name);
1314                         sf.next(":");
1315                         u32 w0 = stoi(sf.next("x"));
1316                         u32 h0 = stoi(sf.next(":"));
1317                         core::dimension2d<u32> dim(w0,h0);
1318                         if (baseimg == NULL) {
1319                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1320                                 baseimg->fill(video::SColor(0,0,0,0));
1321                         }
1322                         while (!sf.at_end()) {
1323                                 u32 x = stoi(sf.next(","));
1324                                 u32 y = stoi(sf.next("="));
1325                                 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1326                                 infostream<<"Adding \""<<filename
1327                                                 <<"\" to combined ("<<x<<","<<y<<")"
1328                                                 <<std::endl;
1329                                 video::IImage *img = generateImage(filename);
1330                                 if (img) {
1331                                         core::dimension2d<u32> dim = img->getDimension();
1332                                         infostream<<"Size "<<dim.Width
1333                                                         <<"x"<<dim.Height<<std::endl;
1334                                         core::position2d<s32> pos_base(x, y);
1335                                         video::IImage *img2 =
1336                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
1337                                         img->copyTo(img2);
1338                                         img->drop();
1339                                         /*img2->copyToWithAlpha(baseimg, pos_base,
1340                                                         core::rect<s32>(v2s32(0,0), dim),
1341                                                         video::SColor(255,255,255,255),
1342                                                         NULL);*/
1343                                         blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1344                                         img2->drop();
1345                                 } else {
1346                                         errorstream << "generateImagePart(): Failed to load image \""
1347                                                 << filename << "\" for [combine" << std::endl;
1348                                 }
1349                         }
1350                 }
1351                 /*
1352                         [brighten
1353                 */
1354                 else if (str_starts_with(part_of_name, "[brighten"))
1355                 {
1356                         if (baseimg == NULL) {
1357                                 errorstream<<"generateImagePart(): baseimg==NULL "
1358                                                 <<"for part_of_name=\""<<part_of_name
1359                                                 <<"\", cancelling."<<std::endl;
1360                                 return false;
1361                         }
1362
1363                         brighten(baseimg);
1364                 }
1365                 /*
1366                         [noalpha
1367                         Make image completely opaque.
1368                         Used for the leaves texture when in old leaves mode, so
1369                         that the transparent parts don't look completely black
1370                         when simple alpha channel is used for rendering.
1371                 */
1372                 else if (str_starts_with(part_of_name, "[noalpha"))
1373                 {
1374                         if (baseimg == NULL){
1375                                 errorstream<<"generateImagePart(): baseimg==NULL "
1376                                                 <<"for part_of_name=\""<<part_of_name
1377                                                 <<"\", cancelling."<<std::endl;
1378                                 return false;
1379                         }
1380
1381                         core::dimension2d<u32> dim = baseimg->getDimension();
1382
1383                         // Set alpha to full
1384                         for (u32 y=0; y<dim.Height; y++)
1385                         for (u32 x=0; x<dim.Width; x++)
1386                         {
1387                                 video::SColor c = baseimg->getPixel(x,y);
1388                                 c.setAlpha(255);
1389                                 baseimg->setPixel(x,y,c);
1390                         }
1391                 }
1392                 /*
1393                         [makealpha:R,G,B
1394                         Convert one color to transparent.
1395                 */
1396                 else if (str_starts_with(part_of_name, "[makealpha:"))
1397                 {
1398                         if (baseimg == NULL) {
1399                                 errorstream<<"generateImagePart(): baseimg == NULL "
1400                                                 <<"for part_of_name=\""<<part_of_name
1401                                                 <<"\", cancelling."<<std::endl;
1402                                 return false;
1403                         }
1404
1405                         Strfnd sf(part_of_name.substr(11));
1406                         u32 r1 = stoi(sf.next(","));
1407                         u32 g1 = stoi(sf.next(","));
1408                         u32 b1 = stoi(sf.next(""));
1409
1410                         core::dimension2d<u32> dim = baseimg->getDimension();
1411
1412                         /*video::IImage *oldbaseimg = baseimg;
1413                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1414                         oldbaseimg->copyTo(baseimg);
1415                         oldbaseimg->drop();*/
1416
1417                         // Set alpha to full
1418                         for (u32 y=0; y<dim.Height; y++)
1419                         for (u32 x=0; x<dim.Width; x++)
1420                         {
1421                                 video::SColor c = baseimg->getPixel(x,y);
1422                                 u32 r = c.getRed();
1423                                 u32 g = c.getGreen();
1424                                 u32 b = c.getBlue();
1425                                 if (!(r == r1 && g == g1 && b == b1))
1426                                         continue;
1427                                 c.setAlpha(0);
1428                                 baseimg->setPixel(x,y,c);
1429                         }
1430                 }
1431                 /*
1432                         [transformN
1433                         Rotates and/or flips the image.
1434
1435                         N can be a number (between 0 and 7) or a transform name.
1436                         Rotations are counter-clockwise.
1437                         0  I      identity
1438                         1  R90    rotate by 90 degrees
1439                         2  R180   rotate by 180 degrees
1440                         3  R270   rotate by 270 degrees
1441                         4  FX     flip X
1442                         5  FXR90  flip X then rotate by 90 degrees
1443                         6  FY     flip Y
1444                         7  FYR90  flip Y then rotate by 90 degrees
1445
1446                         Note: Transform names can be concatenated to produce
1447                         their product (applies the first then the second).
1448                         The resulting transform will be equivalent to one of the
1449                         eight existing ones, though (see: dihedral group).
1450                 */
1451                 else if (str_starts_with(part_of_name, "[transform"))
1452                 {
1453                         if (baseimg == NULL) {
1454                                 errorstream<<"generateImagePart(): baseimg == NULL "
1455                                                 <<"for part_of_name=\""<<part_of_name
1456                                                 <<"\", cancelling."<<std::endl;
1457                                 return false;
1458                         }
1459
1460                         u32 transform = parseImageTransform(part_of_name.substr(10));
1461                         core::dimension2d<u32> dim = imageTransformDimension(
1462                                         transform, baseimg->getDimension());
1463                         video::IImage *image = driver->createImage(
1464                                         baseimg->getColorFormat(), dim);
1465                         sanity_check(image != NULL);
1466                         imageTransform(transform, baseimg, image);
1467                         baseimg->drop();
1468                         baseimg = image;
1469                 }
1470                 /*
1471                         [inventorycube{topimage{leftimage{rightimage
1472                         In every subimage, replace ^ with &.
1473                         Create an "inventory cube".
1474                         NOTE: This should be used only on its own.
1475                         Example (a grass block (not actually used in game):
1476                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1477                 */
1478                 else if (str_starts_with(part_of_name, "[inventorycube"))
1479                 {
1480                         if (baseimg != NULL){
1481                                 errorstream<<"generateImagePart(): baseimg != NULL "
1482                                                 <<"for part_of_name=\""<<part_of_name
1483                                                 <<"\", cancelling."<<std::endl;
1484                                 return false;
1485                         }
1486
1487                         str_replace(part_of_name, '&', '^');
1488                         Strfnd sf(part_of_name);
1489                         sf.next("{");
1490                         std::string imagename_top = sf.next("{");
1491                         std::string imagename_left = sf.next("{");
1492                         std::string imagename_right = sf.next("{");
1493
1494                         // Generate images for the faces of the cube
1495                         video::IImage *img_top = generateImage(imagename_top);
1496                         video::IImage *img_left = generateImage(imagename_left);
1497                         video::IImage *img_right = generateImage(imagename_right);
1498
1499                         if (img_top == NULL || img_left == NULL || img_right == NULL) {
1500                                 errorstream << "generateImagePart(): Failed to create textures"
1501                                                 << " for inventorycube \"" << part_of_name << "\""
1502                                                 << std::endl;
1503                                 baseimg = generateImage(imagename_top);
1504                                 return true;
1505                         }
1506
1507 #ifdef __ANDROID__
1508                         assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1509                         assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1510
1511                         assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1512                         assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1513
1514                         assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1515                         assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1516 #endif
1517
1518                         // Create textures from images
1519                         video::ITexture *texture_top = driver->addTexture(
1520                                         (imagename_top + "__temp__").c_str(), img_top);
1521                         video::ITexture *texture_left = driver->addTexture(
1522                                         (imagename_left + "__temp__").c_str(), img_left);
1523                         video::ITexture *texture_right = driver->addTexture(
1524                                         (imagename_right + "__temp__").c_str(), img_right);
1525                         FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1526
1527                         // Drop images
1528                         img_top->drop();
1529                         img_left->drop();
1530                         img_right->drop();
1531
1532                         /*
1533                                 Draw a cube mesh into a render target texture
1534                         */
1535                         scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1536                         setMeshColor(cube, video::SColor(255, 255, 255, 255));
1537                         cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1538                         cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1539                         cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1540                         cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1541                         cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1542                         cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1543
1544                         TextureFromMeshParams params;
1545                         params.mesh = cube;
1546                         params.dim.set(64, 64);
1547                         params.rtt_texture_name = part_of_name + "_RTT";
1548                         // We will delete the rtt texture ourselves
1549                         params.delete_texture_on_shutdown = false;
1550                         params.camera_position.set(0, 1.0, -1.5);
1551                         params.camera_position.rotateXZBy(45);
1552                         params.camera_lookat.set(0, 0, 0);
1553                         // Set orthogonal projection
1554                         params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1555                                         1.65, 1.65, 0, 100);
1556
1557                         params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1558                         params.light_position.set(10, 100, -50);
1559                         params.light_color.set(1.0, 0.5, 0.5, 0.5);
1560                         params.light_radius = 1000;
1561
1562                         video::ITexture *rtt = generateTextureFromMesh(params);
1563
1564                         // Drop mesh
1565                         cube->drop();
1566
1567                         // Free textures
1568                         driver->removeTexture(texture_top);
1569                         driver->removeTexture(texture_left);
1570                         driver->removeTexture(texture_right);
1571
1572                         if (rtt == NULL) {
1573                                 baseimg = generateImage(imagename_top);
1574                                 return true;
1575                         }
1576
1577                         // Create image of render target
1578                         video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1579                         FATAL_ERROR_IF(!image, "Could not create image of render target");
1580
1581                         // Cleanup texture
1582                         driver->removeTexture(rtt);
1583
1584                         baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1585
1586                         if (image) {
1587                                 image->copyTo(baseimg);
1588                                 image->drop();
1589                         }
1590                 }
1591                 /*
1592                         [lowpart:percent:filename
1593                         Adds the lower part of a texture
1594                 */
1595                 else if (str_starts_with(part_of_name, "[lowpart:"))
1596                 {
1597                         Strfnd sf(part_of_name);
1598                         sf.next(":");
1599                         u32 percent = stoi(sf.next(":"));
1600                         std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1601
1602                         if (baseimg == NULL)
1603                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1604                         video::IImage *img = generateImage(filename);
1605                         if (img)
1606                         {
1607                                 core::dimension2d<u32> dim = img->getDimension();
1608                                 core::position2d<s32> pos_base(0, 0);
1609                                 video::IImage *img2 =
1610                                                 driver->createImage(video::ECF_A8R8G8B8, dim);
1611                                 img->copyTo(img2);
1612                                 img->drop();
1613                                 core::position2d<s32> clippos(0, 0);
1614                                 clippos.Y = dim.Height * (100-percent) / 100;
1615                                 core::dimension2d<u32> clipdim = dim;
1616                                 clipdim.Height = clipdim.Height * percent / 100 + 1;
1617                                 core::rect<s32> cliprect(clippos, clipdim);
1618                                 img2->copyToWithAlpha(baseimg, pos_base,
1619                                                 core::rect<s32>(v2s32(0,0), dim),
1620                                                 video::SColor(255,255,255,255),
1621                                                 &cliprect);
1622                                 img2->drop();
1623                         }
1624                 }
1625                 /*
1626                         [verticalframe:N:I
1627                         Crops a frame of a vertical animation.
1628                         N = frame count, I = frame index
1629                 */
1630                 else if (str_starts_with(part_of_name, "[verticalframe:"))
1631                 {
1632                         Strfnd sf(part_of_name);
1633                         sf.next(":");
1634                         u32 frame_count = stoi(sf.next(":"));
1635                         u32 frame_index = stoi(sf.next(":"));
1636
1637                         if (baseimg == NULL){
1638                                 errorstream<<"generateImagePart(): baseimg != NULL "
1639                                                 <<"for part_of_name=\""<<part_of_name
1640                                                 <<"\", cancelling."<<std::endl;
1641                                 return false;
1642                         }
1643
1644                         v2u32 frame_size = baseimg->getDimension();
1645                         frame_size.Y /= frame_count;
1646
1647                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1648                                         frame_size);
1649                         if (!img){
1650                                 errorstream<<"generateImagePart(): Could not create image "
1651                                                 <<"for part_of_name=\""<<part_of_name
1652                                                 <<"\", cancelling."<<std::endl;
1653                                 return false;
1654                         }
1655
1656                         // Fill target image with transparency
1657                         img->fill(video::SColor(0,0,0,0));
1658
1659                         core::dimension2d<u32> dim = frame_size;
1660                         core::position2d<s32> pos_dst(0, 0);
1661                         core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1662                         baseimg->copyToWithAlpha(img, pos_dst,
1663                                         core::rect<s32>(pos_src, dim),
1664                                         video::SColor(255,255,255,255),
1665                                         NULL);
1666                         // Replace baseimg
1667                         baseimg->drop();
1668                         baseimg = img;
1669                 }
1670                 /*
1671                         [mask:filename
1672                         Applies a mask to an image
1673                 */
1674                 else if (str_starts_with(part_of_name, "[mask:"))
1675                 {
1676                         if (baseimg == NULL) {
1677                                 errorstream << "generateImage(): baseimg == NULL "
1678                                                 << "for part_of_name=\"" << part_of_name
1679                                                 << "\", cancelling." << std::endl;
1680                                 return false;
1681                         }
1682                         Strfnd sf(part_of_name);
1683                         sf.next(":");
1684                         std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1685
1686                         video::IImage *img = generateImage(filename);
1687                         if (img) {
1688                                 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1689                                                 img->getDimension());
1690                                 img->drop();
1691                         } else {
1692                                 errorstream << "generateImage(): Failed to load \""
1693                                                 << filename << "\".";
1694                         }
1695                 }
1696                 /*
1697                 [multiply:color
1698                         multiplys a given color to any pixel of an image
1699                         color = color as ColorString
1700                 */
1701                 else if (str_starts_with(part_of_name, "[multiply:")) {
1702                         Strfnd sf(part_of_name);
1703                         sf.next(":");
1704                         std::string color_str = sf.next(":");
1705
1706                         if (baseimg == NULL) {
1707                                 errorstream << "generateImagePart(): baseimg != NULL "
1708                                                 << "for part_of_name=\"" << part_of_name
1709                                                 << "\", cancelling." << std::endl;
1710                                 return false;
1711                         }
1712
1713                         video::SColor color;
1714
1715                         if (!parseColorString(color_str, color, false))
1716                                 return false;
1717
1718                         apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1719                 }
1720                 /*
1721                         [colorize:color
1722                         Overlays image with given color
1723                         color = color as ColorString
1724                 */
1725                 else if (str_starts_with(part_of_name, "[colorize:"))
1726                 {
1727                         Strfnd sf(part_of_name);
1728                         sf.next(":");
1729                         std::string color_str = sf.next(":");
1730                         std::string ratio_str = sf.next(":");
1731
1732                         if (baseimg == NULL) {
1733                                 errorstream << "generateImagePart(): baseimg != NULL "
1734                                                 << "for part_of_name=\"" << part_of_name
1735                                                 << "\", cancelling." << std::endl;
1736                                 return false;
1737                         }
1738
1739                         video::SColor color;
1740                         int ratio = -1;
1741                         bool keep_alpha = false;
1742
1743                         if (!parseColorString(color_str, color, false))
1744                                 return false;
1745
1746                         if (is_number(ratio_str))
1747                                 ratio = mystoi(ratio_str, 0, 255);
1748                         else if (ratio_str == "alpha")
1749                                 keep_alpha = true;
1750
1751                         apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1752                 }
1753                 /*
1754                         [applyfiltersformesh
1755                         Internal modifier
1756                 */
1757                 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1758                 {
1759                         // Apply the "clean transparent" filter, if configured.
1760                         if (g_settings->getBool("texture_clean_transparent"))
1761                                 imageCleanTransparent(baseimg, 127);
1762
1763                         /* Upscale textures to user's requested minimum size.  This is a trick to make
1764                          * filters look as good on low-res textures as on high-res ones, by making
1765                          * low-res textures BECOME high-res ones.  This is helpful for worlds that
1766                          * mix high- and low-res textures, or for mods with least-common-denominator
1767                          * textures that don't have the resources to offer high-res alternatives.
1768                          */
1769                         s32 scaleto = g_settings->getS32("texture_min_size");
1770                         if (scaleto > 1) {
1771                                 const core::dimension2d<u32> dim = baseimg->getDimension();
1772
1773                                 /* Calculate scaling needed to make the shortest texture dimension
1774                                  * equal to the target minimum.  If e.g. this is a vertical frames
1775                                  * animation, the short dimension will be the real size.
1776                                  */
1777                                 if ((dim.Width == 0) || (dim.Height == 0)) {
1778                                         errorstream << "generateImagePart(): Illegal 0 dimension "
1779                                                 << "for part_of_name=\""<< part_of_name
1780                                                 << "\", cancelling." << std::endl;
1781                                         return false;
1782                                 }
1783                                 u32 xscale = scaleto / dim.Width;
1784                                 u32 yscale = scaleto / dim.Height;
1785                                 u32 scale = (xscale > yscale) ? xscale : yscale;
1786
1787                                 // Never downscale; only scale up by 2x or more.
1788                                 if (scale > 1) {
1789                                         u32 w = scale * dim.Width;
1790                                         u32 h = scale * dim.Height;
1791                                         const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1792                                         video::IImage *newimg = driver->createImage(
1793                                                         baseimg->getColorFormat(), newdim);
1794                                         baseimg->copyToScaling(newimg);
1795                                         baseimg->drop();
1796                                         baseimg = newimg;
1797                                 }
1798                         }
1799                 }
1800                 /*
1801                         [resize:WxH
1802                         Resizes the base image to the given dimensions
1803                 */
1804                 else if (str_starts_with(part_of_name, "[resize"))
1805                 {
1806                         if (baseimg == NULL) {
1807                                 errorstream << "generateImagePart(): baseimg == NULL "
1808                                                 << "for part_of_name=\""<< part_of_name
1809                                                 << "\", cancelling." << std::endl;
1810                                 return false;
1811                         }
1812
1813                         Strfnd sf(part_of_name);
1814                         sf.next(":");
1815                         u32 width = stoi(sf.next("x"));
1816                         u32 height = stoi(sf.next(""));
1817                         core::dimension2d<u32> dim(width, height);
1818
1819                         video::IImage *image = RenderingEngine::get_video_driver()->
1820                                 createImage(video::ECF_A8R8G8B8, dim);
1821                         baseimg->copyToScaling(image);
1822                         baseimg->drop();
1823                         baseimg = image;
1824                 }
1825                 /*
1826                         [opacity:R
1827                         Makes the base image transparent according to the given ratio.
1828                         R must be between 0 and 255.
1829                         0 means totally transparent.
1830                         255 means totally opaque.
1831                 */
1832                 else if (str_starts_with(part_of_name, "[opacity:")) {
1833                         if (baseimg == NULL) {
1834                                 errorstream << "generateImagePart(): baseimg == NULL "
1835                                                 << "for part_of_name=\"" << part_of_name
1836                                                 << "\", cancelling." << std::endl;
1837                                 return false;
1838                         }
1839
1840                         Strfnd sf(part_of_name);
1841                         sf.next(":");
1842
1843                         u32 ratio = mystoi(sf.next(""), 0, 255);
1844
1845                         core::dimension2d<u32> dim = baseimg->getDimension();
1846
1847                         for (u32 y = 0; y < dim.Height; y++)
1848                         for (u32 x = 0; x < dim.Width; x++)
1849                         {
1850                                 video::SColor c = baseimg->getPixel(x, y);
1851                                 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1852                                 baseimg->setPixel(x, y, c);
1853                         }
1854                 }
1855                 /*
1856                         [invert:mode
1857                         Inverts the given channels of the base image.
1858                         Mode may contain the characters "r", "g", "b", "a".
1859                         Only the channels that are mentioned in the mode string
1860                         will be inverted.
1861                 */
1862                 else if (str_starts_with(part_of_name, "[invert:")) {
1863                         if (baseimg == NULL) {
1864                                 errorstream << "generateImagePart(): baseimg == NULL "
1865                                                 << "for part_of_name=\"" << part_of_name
1866                                                 << "\", cancelling." << std::endl;
1867                                 return false;
1868                         }
1869
1870                         Strfnd sf(part_of_name);
1871                         sf.next(":");
1872
1873                         std::string mode = sf.next("");
1874                         u32 mask = 0;
1875                         if (mode.find('a') != std::string::npos)
1876                                 mask |= 0xff000000UL;
1877                         if (mode.find('r') != std::string::npos)
1878                                 mask |= 0x00ff0000UL;
1879                         if (mode.find('g') != std::string::npos)
1880                                 mask |= 0x0000ff00UL;
1881                         if (mode.find('b') != std::string::npos)
1882                                 mask |= 0x000000ffUL;
1883
1884                         core::dimension2d<u32> dim = baseimg->getDimension();
1885
1886                         for (u32 y = 0; y < dim.Height; y++)
1887                         for (u32 x = 0; x < dim.Width; x++)
1888                         {
1889                                 video::SColor c = baseimg->getPixel(x, y);
1890                                 c.color ^= mask;
1891                                 baseimg->setPixel(x, y, c);
1892                         }
1893                 }
1894                 /*
1895                         [sheet:WxH:X,Y
1896                         Retrieves a tile at position X,Y (in tiles)
1897                         from the base image it assumes to be a
1898                         tilesheet with dimensions W,H (in tiles).
1899                 */
1900                 else if (part_of_name.substr(0,7) == "[sheet:") {
1901                         if (baseimg == NULL) {
1902                                 errorstream << "generateImagePart(): baseimg != NULL "
1903                                                 << "for part_of_name=\"" << part_of_name
1904                                                 << "\", cancelling." << std::endl;
1905                                 return false;
1906                         }
1907
1908                         Strfnd sf(part_of_name);
1909                         sf.next(":");
1910                         u32 w0 = stoi(sf.next("x"));
1911                         u32 h0 = stoi(sf.next(":"));
1912                         u32 x0 = stoi(sf.next(","));
1913                         u32 y0 = stoi(sf.next(":"));
1914
1915                         core::dimension2d<u32> img_dim = baseimg->getDimension();
1916                         core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1917
1918                         video::IImage *img = driver->createImage(
1919                                         video::ECF_A8R8G8B8, tile_dim);
1920                         if (!img) {
1921                                 errorstream << "generateImagePart(): Could not create image "
1922                                                 << "for part_of_name=\"" << part_of_name
1923                                                 << "\", cancelling." << std::endl;
1924                                 return false;
1925                         }
1926
1927                         img->fill(video::SColor(0,0,0,0));
1928                         v2u32 vdim(tile_dim);
1929                         core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1930                         baseimg->copyToWithAlpha(img, v2s32(0), rect,
1931                                         video::SColor(255,255,255,255), NULL);
1932
1933                         // Replace baseimg
1934                         baseimg->drop();
1935                         baseimg = img;
1936                 }
1937                 else
1938                 {
1939                         errorstream << "generateImagePart(): Invalid "
1940                                         " modification: \"" << part_of_name << "\"" << std::endl;
1941                 }
1942         }
1943
1944         return true;
1945 }
1946
1947 /*
1948         Draw an image on top of an another one, using the alpha channel of the
1949         source image
1950
1951         This exists because IImage::copyToWithAlpha() doesn't seem to always
1952         work.
1953 */
1954 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1955                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1956 {
1957         for (u32 y0=0; y0<size.Y; y0++)
1958         for (u32 x0=0; x0<size.X; x0++)
1959         {
1960                 s32 src_x = src_pos.X + x0;
1961                 s32 src_y = src_pos.Y + y0;
1962                 s32 dst_x = dst_pos.X + x0;
1963                 s32 dst_y = dst_pos.Y + y0;
1964                 video::SColor src_c = src->getPixel(src_x, src_y);
1965                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1966                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1967                 dst->setPixel(dst_x, dst_y, dst_c);
1968         }
1969 }
1970
1971 /*
1972         Draw an image on top of an another one, using the alpha channel of the
1973         source image; only modify fully opaque pixels in destinaion
1974 */
1975 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1976                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1977 {
1978         for (u32 y0=0; y0<size.Y; y0++)
1979         for (u32 x0=0; x0<size.X; x0++)
1980         {
1981                 s32 src_x = src_pos.X + x0;
1982                 s32 src_y = src_pos.Y + y0;
1983                 s32 dst_x = dst_pos.X + x0;
1984                 s32 dst_y = dst_pos.Y + y0;
1985                 video::SColor src_c = src->getPixel(src_x, src_y);
1986                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1987                 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1988                 {
1989                         dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1990                         dst->setPixel(dst_x, dst_y, dst_c);
1991                 }
1992         }
1993 }
1994
1995 // This function has been disabled because it is currently unused.
1996 // Feel free to re-enable if you find it handy.
1997 #if 0
1998 /*
1999         Draw an image on top of an another one, using the specified ratio
2000         modify all partially-opaque pixels in the destination.
2001 */
2002 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2003                 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2004 {
2005         for (u32 y0 = 0; y0 < size.Y; y0++)
2006         for (u32 x0 = 0; x0 < size.X; x0++)
2007         {
2008                 s32 src_x = src_pos.X + x0;
2009                 s32 src_y = src_pos.Y + y0;
2010                 s32 dst_x = dst_pos.X + x0;
2011                 s32 dst_y = dst_pos.Y + y0;
2012                 video::SColor src_c = src->getPixel(src_x, src_y);
2013                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2014                 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2015                 {
2016                         if (ratio == -1)
2017                                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2018                         else
2019                                 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2020                         dst->setPixel(dst_x, dst_y, dst_c);
2021                 }
2022         }
2023 }
2024 #endif
2025
2026 /*
2027         Apply color to destination
2028 */
2029 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2030                 const video::SColor &color, int ratio, bool keep_alpha)
2031 {
2032         u32 alpha = color.getAlpha();
2033         video::SColor dst_c;
2034         if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2035                 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2036                         dst_c = color;
2037                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2038                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2039                                 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2040                                 if (dst_alpha > 0) {
2041                                         dst_c.setAlpha(dst_alpha * alpha / 255);
2042                                         dst->setPixel(x, y, dst_c);
2043                                 }
2044                         }
2045                 } else { // replace the color including the alpha
2046                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2047                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2048                                 if (dst->getPixel(x, y).getAlpha() > 0)
2049                                         dst->setPixel(x, y, color);
2050                 }
2051         } else {  // interpolate between the color and destination
2052                 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2053                 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2054                 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2055                         dst_c = dst->getPixel(x, y);
2056                         if (dst_c.getAlpha() > 0) {
2057                                 dst_c = color.getInterpolated(dst_c, interp);
2058                                 dst->setPixel(x, y, dst_c);
2059                         }
2060                 }
2061         }
2062 }
2063
2064 /*
2065         Apply color to destination
2066 */
2067 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2068                 const video::SColor &color)
2069 {
2070         video::SColor dst_c;
2071
2072         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2073         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2074                 dst_c = dst->getPixel(x, y);
2075                 dst_c.set(
2076                                 dst_c.getAlpha(),
2077                                 (dst_c.getRed() * color.getRed()) / 255,
2078                                 (dst_c.getGreen() * color.getGreen()) / 255,
2079                                 (dst_c.getBlue() * color.getBlue()) / 255
2080                                 );
2081                 dst->setPixel(x, y, dst_c);
2082         }
2083 }
2084
2085 /*
2086         Apply mask to destination
2087 */
2088 static void apply_mask(video::IImage *mask, video::IImage *dst,
2089                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2090 {
2091         for (u32 y0 = 0; y0 < size.Y; y0++) {
2092                 for (u32 x0 = 0; x0 < size.X; x0++) {
2093                         s32 mask_x = x0 + mask_pos.X;
2094                         s32 mask_y = y0 + mask_pos.Y;
2095                         s32 dst_x = x0 + dst_pos.X;
2096                         s32 dst_y = y0 + dst_pos.Y;
2097                         video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2098                         video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2099                         dst_c.color &= mask_c.color;
2100                         dst->setPixel(dst_x, dst_y, dst_c);
2101                 }
2102         }
2103 }
2104
2105 static void draw_crack(video::IImage *crack, video::IImage *dst,
2106                 bool use_overlay, s32 frame_count, s32 progression,
2107                 video::IVideoDriver *driver)
2108 {
2109         // Dimension of destination image
2110         core::dimension2d<u32> dim_dst = dst->getDimension();
2111         // Dimension of original image
2112         core::dimension2d<u32> dim_crack = crack->getDimension();
2113         // Count of crack stages
2114         s32 crack_count = dim_crack.Height / dim_crack.Width;
2115         // Limit frame_count
2116         if (frame_count > (s32) dim_dst.Height)
2117                 frame_count = dim_dst.Height;
2118         if (frame_count < 1)
2119                 frame_count = 1;
2120         // Limit progression
2121         if (progression > crack_count-1)
2122                 progression = crack_count-1;
2123         // Dimension of a single crack stage
2124         core::dimension2d<u32> dim_crack_cropped(
2125                 dim_crack.Width,
2126                 dim_crack.Width
2127         );
2128         // Dimension of the scaled crack stage,
2129         // which is the same as the dimension of a single destination frame
2130         core::dimension2d<u32> dim_crack_scaled(
2131                 dim_dst.Width,
2132                 dim_dst.Height / frame_count
2133         );
2134         // Create cropped and scaled crack images
2135         video::IImage *crack_cropped = driver->createImage(
2136                         video::ECF_A8R8G8B8, dim_crack_cropped);
2137         video::IImage *crack_scaled = driver->createImage(
2138                         video::ECF_A8R8G8B8, dim_crack_scaled);
2139
2140         if (crack_cropped && crack_scaled)
2141         {
2142                 // Crop crack image
2143                 v2s32 pos_crack(0, progression*dim_crack.Width);
2144                 crack->copyTo(crack_cropped,
2145                                 v2s32(0,0),
2146                                 core::rect<s32>(pos_crack, dim_crack_cropped));
2147                 // Scale crack image by copying
2148                 crack_cropped->copyToScaling(crack_scaled);
2149                 // Copy or overlay crack image onto each frame
2150                 for (s32 i = 0; i < frame_count; ++i)
2151                 {
2152                         v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2153                         if (use_overlay)
2154                         {
2155                                 blit_with_alpha_overlay(crack_scaled, dst,
2156                                                 v2s32(0,0), dst_pos,
2157                                                 dim_crack_scaled);
2158                         }
2159                         else
2160                         {
2161                                 blit_with_alpha(crack_scaled, dst,
2162                                                 v2s32(0,0), dst_pos,
2163                                                 dim_crack_scaled);
2164                         }
2165                 }
2166         }
2167
2168         if (crack_scaled)
2169                 crack_scaled->drop();
2170
2171         if (crack_cropped)
2172                 crack_cropped->drop();
2173 }
2174
2175 void brighten(video::IImage *image)
2176 {
2177         if (image == NULL)
2178                 return;
2179
2180         core::dimension2d<u32> dim = image->getDimension();
2181
2182         for (u32 y=0; y<dim.Height; y++)
2183         for (u32 x=0; x<dim.Width; x++)
2184         {
2185                 video::SColor c = image->getPixel(x,y);
2186                 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2187                 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2188                 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2189                 image->setPixel(x,y,c);
2190         }
2191 }
2192
2193 u32 parseImageTransform(const std::string& s)
2194 {
2195         int total_transform = 0;
2196
2197         std::string transform_names[8];
2198         transform_names[0] = "i";
2199         transform_names[1] = "r90";
2200         transform_names[2] = "r180";
2201         transform_names[3] = "r270";
2202         transform_names[4] = "fx";
2203         transform_names[6] = "fy";
2204
2205         std::size_t pos = 0;
2206         while(pos < s.size())
2207         {
2208                 int transform = -1;
2209                 for (int i = 0; i <= 7; ++i)
2210                 {
2211                         const std::string &name_i = transform_names[i];
2212
2213                         if (s[pos] == ('0' + i))
2214                         {
2215                                 transform = i;
2216                                 pos++;
2217                                 break;
2218                         }
2219
2220                         if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2221                                 transform = i;
2222                                 pos += name_i.size();
2223                                 break;
2224                         }
2225                 }
2226                 if (transform < 0)
2227                         break;
2228
2229                 // Multiply total_transform and transform in the group D4
2230                 int new_total = 0;
2231                 if (transform < 4)
2232                         new_total = (transform + total_transform) % 4;
2233                 else
2234                         new_total = (transform - total_transform + 8) % 4;
2235                 if ((transform >= 4) ^ (total_transform >= 4))
2236                         new_total += 4;
2237
2238                 total_transform = new_total;
2239         }
2240         return total_transform;
2241 }
2242
2243 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2244 {
2245         if (transform % 2 == 0)
2246                 return dim;
2247
2248         return core::dimension2d<u32>(dim.Height, dim.Width);
2249 }
2250
2251 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2252 {
2253         if (src == NULL || dst == NULL)
2254                 return;
2255
2256         core::dimension2d<u32> dstdim = dst->getDimension();
2257
2258         // Pre-conditions
2259         assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2260         assert(transform <= 7);
2261
2262         /*
2263                 Compute the transformation from source coordinates (sx,sy)
2264                 to destination coordinates (dx,dy).
2265         */
2266         int sxn = 0;
2267         int syn = 2;
2268         if (transform == 0)         // identity
2269                 sxn = 0, syn = 2;  //   sx = dx, sy = dy
2270         else if (transform == 1)    // rotate by 90 degrees ccw
2271                 sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
2272         else if (transform == 2)    // rotate by 180 degrees
2273                 sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
2274         else if (transform == 3)    // rotate by 270 degrees ccw
2275                 sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
2276         else if (transform == 4)    // flip x
2277                 sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
2278         else if (transform == 5)    // flip x then rotate by 90 degrees ccw
2279                 sxn = 2, syn = 0;  //   sx = dy, sy = dx
2280         else if (transform == 6)    // flip y
2281                 sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
2282         else if (transform == 7)    // flip y then rotate by 90 degrees ccw
2283                 sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
2284
2285         for (u32 dy=0; dy<dstdim.Height; dy++)
2286         for (u32 dx=0; dx<dstdim.Width; dx++)
2287         {
2288                 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2289                 u32 sx = entries[sxn];
2290                 u32 sy = entries[syn];
2291                 video::SColor c = src->getPixel(sx,sy);
2292                 dst->setPixel(dx,dy,c);
2293         }
2294 }
2295
2296 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2297 {
2298         if (isKnownSourceImage("override_normal.png"))
2299                 return getTexture("override_normal.png");
2300         std::string fname_base = name;
2301         static const char *normal_ext = "_normal.png";
2302         static const u32 normal_ext_size = strlen(normal_ext);
2303         size_t pos = fname_base.find('.');
2304         std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2305         if (isKnownSourceImage(fname_normal)) {
2306                 // look for image extension and replace it
2307                 size_t i = 0;
2308                 while ((i = fname_base.find('.', i)) != std::string::npos) {
2309                         fname_base.replace(i, 4, normal_ext);
2310                         i += normal_ext_size;
2311                 }
2312                 return getTexture(fname_base);
2313         }
2314         return NULL;
2315 }
2316
2317 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2318 {
2319         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2320         video::SColor c(0, 0, 0, 0);
2321         video::ITexture *texture = getTexture(name);
2322         video::IImage *image = driver->createImage(texture,
2323                 core::position2d<s32>(0, 0),
2324                 texture->getOriginalSize());
2325         u32 total = 0;
2326         u32 tR = 0;
2327         u32 tG = 0;
2328         u32 tB = 0;
2329         core::dimension2d<u32> dim = image->getDimension();
2330         u16 step = 1;
2331         if (dim.Width > 16)
2332                 step = dim.Width / 16;
2333         for (u16 x = 0; x < dim.Width; x += step) {
2334                 for (u16 y = 0; y < dim.Width; y += step) {
2335                         c = image->getPixel(x,y);
2336                         if (c.getAlpha() > 0) {
2337                                 total++;
2338                                 tR += c.getRed();
2339                                 tG += c.getGreen();
2340                                 tB += c.getBlue();
2341                         }
2342                 }
2343         }
2344         image->drop();
2345         if (total > 0) {
2346                 c.setRed(tR / total);
2347                 c.setGreen(tG / total);
2348                 c.setBlue(tB / total);
2349         }
2350         c.setAlpha(255);
2351         return c;
2352 }
2353
2354
2355 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2356 {
2357         std::string tname = "__shaderFlagsTexture";
2358         tname += normalmap_present ? "1" : "0";
2359
2360         if (isKnownSourceImage(tname)) {
2361                 return getTexture(tname);
2362         }
2363
2364         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2365         video::IImage *flags_image = driver->createImage(
2366                 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2367         sanity_check(flags_image != NULL);
2368         video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2369         flags_image->setPixel(0, 0, c);
2370         insertSourceImage(tname, flags_image);
2371         flags_image->drop();
2372         return getTexture(tname);
2373
2374 }