3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
23 #include <ICameraSceneNode.h>
24 #include "util/string.h"
25 #include "util/container.h"
26 #include "util/thread.h"
31 #include "util/strfnd.h"
32 #include "imagefilters.h"
33 #include "guiscalingfilter.h"
34 #include "renderingengine.h"
42 A cache from texture name to texture path
44 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
47 Replaces the filename extension.
49 std::string image = "a/image.png"
50 replace_ext(image, "jpg")
51 -> image = "a/image.jpg"
52 Returns true on success.
54 static bool replace_ext(std::string &path, const char *ext)
58 // Find place of last dot, fail if \ or / found.
60 for (s32 i=path.size()-1; i>=0; i--)
68 if (path[i] == '\\' || path[i] == '/')
71 // If not found, return an empty string
74 // Else make the new path
75 path = path.substr(0, last_dot_i+1) + ext;
80 Find out the full path of an image by trying different filename
85 std::string getImagePath(std::string path)
87 // A NULL-ended list of possible image extensions
88 const char *extensions[] = {
89 "png", "jpg", "bmp", "tga",
90 "pcx", "ppm", "psd", "wal", "rgb",
93 // If there is no extension, add one
94 if (removeStringEnd(path, extensions).empty())
96 // Check paths until something is found to exist
97 const char **ext = extensions;
99 bool r = replace_ext(path, *ext);
102 if (fs::PathExists(path))
105 while((++ext) != NULL);
111 Gets the path to a texture by first checking if the texture exists
112 in texture_path and if not, using the data path.
114 Checks all supported extensions by replacing the original extension.
116 If not found, returns "".
118 Utilizes a thread-safe cache.
120 std::string getTexturePath(const std::string &filename)
122 std::string fullpath;
126 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
131 Check from texture_path
133 for (const auto &path : getTextureDirs()) {
134 std::string testpath = path + DIR_DELIM;
135 testpath.append(filename);
136 // Check all filename extensions. Returns "" if not found.
137 fullpath = getImagePath(testpath);
138 if (!fullpath.empty())
143 Check from default data directory
145 if (fullpath.empty())
147 std::string base_path = porting::path_share + DIR_DELIM + "textures"
148 + DIR_DELIM + "base" + DIR_DELIM + "pack";
149 std::string testpath = base_path + DIR_DELIM + filename;
150 // Check all filename extensions. Returns "" if not found.
151 fullpath = getImagePath(testpath);
154 // Add to cache (also an empty result is cached)
155 g_texturename_to_path_cache.set(filename, fullpath);
161 void clearTextureNameCache()
163 g_texturename_to_path_cache.clear();
167 Stores internal information about a texture.
173 video::ITexture *texture;
176 const std::string &name_,
177 video::ITexture *texture_=NULL
186 SourceImageCache: A cache used for storing source images.
189 class SourceImageCache
192 ~SourceImageCache() {
193 for (auto &m_image : m_images) {
194 m_image.second->drop();
198 void insert(const std::string &name, video::IImage *img, bool prefer_local)
200 assert(img); // Pre-condition
202 std::map<std::string, video::IImage*>::iterator n;
203 n = m_images.find(name);
204 if (n != m_images.end()){
209 video::IImage* toadd = img;
210 bool need_to_grab = true;
212 // Try to use local texture instead if asked to
214 std::string path = getTexturePath(name);
216 video::IImage *img2 = RenderingEngine::get_video_driver()->
217 createImageFromFile(path.c_str());
220 need_to_grab = false;
227 m_images[name] = toadd;
229 video::IImage* get(const std::string &name)
231 std::map<std::string, video::IImage*>::iterator n;
232 n = m_images.find(name);
233 if (n != m_images.end())
237 // Primarily fetches from cache, secondarily tries to read from filesystem
238 video::IImage *getOrLoad(const std::string &name)
240 std::map<std::string, video::IImage*>::iterator n;
241 n = m_images.find(name);
242 if (n != m_images.end()){
243 n->second->grab(); // Grab for caller
246 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
247 std::string path = getTexturePath(name);
249 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
250 <<name<<"\""<<std::endl;
253 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
255 video::IImage *img = driver->createImageFromFile(path.c_str());
258 m_images[name] = img;
259 img->grab(); // Grab for caller
264 std::map<std::string, video::IImage*> m_images;
271 class TextureSource : public IWritableTextureSource
275 virtual ~TextureSource();
279 Now, assume a texture with the id 1 exists, and has the name
280 "stone.png^mineral1".
281 Then a random thread calls getTextureId for a texture called
282 "stone.png^mineral1^crack0".
283 ...Now, WTF should happen? Well:
284 - getTextureId strips off stuff recursively from the end until
285 the remaining part is found, or nothing is left when
286 something is stripped out
288 But it is slow to search for textures by names and modify them
290 - ContentFeatures is made to contain ids for the basic plain
292 - Crack textures can be slow by themselves, but the framework
296 - Assume a texture with the id 1 exists, and has the name
297 "stone.png^mineral_coal.png".
298 - Now getNodeTile() stumbles upon a node which uses
299 texture id 1, and determines that MATERIAL_FLAG_CRACK
300 must be applied to the tile
301 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
302 has received the current crack level 0 from the client. It
303 finds out the name of the texture with getTextureName(1),
304 appends "^crack0" to it and gets a new texture id with
305 getTextureId("stone.png^mineral_coal.png^crack0").
310 Gets a texture id from cache or
311 - if main thread, generates the texture, adds to cache and returns id.
312 - if other thread, adds to request queue and waits for main thread.
314 The id 0 points to a NULL texture. It is returned in case of error.
316 u32 getTextureId(const std::string &name);
318 // Finds out the name of a cached texture.
319 std::string getTextureName(u32 id);
322 If texture specified by the name pointed by the id doesn't
323 exist, create it, then return the cached texture.
325 Can be called from any thread. If called from some other thread
326 and not found in cache, the call is queued to the main thread
329 video::ITexture* getTexture(u32 id);
331 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
334 Get a texture specifically intended for mesh
335 application, i.e. not HUD, compositing, or other 2D
336 use. This texture may be a different size and may
337 have had additional filters applied.
339 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
341 virtual Palette* getPalette(const std::string &name);
343 bool isKnownSourceImage(const std::string &name)
345 bool is_known = false;
346 bool cache_found = m_source_image_existence.get(name, &is_known);
349 // Not found in cache; find out if a local file exists
350 is_known = (!getTexturePath(name).empty());
351 m_source_image_existence.set(name, is_known);
355 // Processes queued texture requests from other threads.
356 // Shall be called from the main thread.
359 // Insert an image into the cache without touching the filesystem.
360 // Shall be called from the main thread.
361 void insertSourceImage(const std::string &name, video::IImage *img);
363 // Rebuild images and textures from the current set of source images
364 // Shall be called from the main thread.
365 void rebuildImagesAndTextures();
367 video::ITexture* getNormalTexture(const std::string &name);
368 video::SColor getTextureAverageColor(const std::string &name);
369 video::ITexture *getShaderFlagsTexture(bool normamap_present);
373 // The id of the thread that is allowed to use irrlicht directly
374 std::thread::id m_main_thread;
376 // Cache of source images
377 // This should be only accessed from the main thread
378 SourceImageCache m_sourcecache;
380 // Generate a texture
381 u32 generateTexture(const std::string &name);
383 // Generate image based on a string like "stone.png" or "[crack:1:0".
384 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
385 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
387 /*! Generates an image from a full string like
388 * "stone.png^mineral_coal.png^[crack:1:0".
389 * Shall be called from the main thread.
390 * The returned Image should be dropped.
392 video::IImage* generateImage(const std::string &name);
394 // Thread-safe cache of what source images are known (true = known)
395 MutexedMap<std::string, bool> m_source_image_existence;
397 // A texture id is index in this array.
398 // The first position contains a NULL texture.
399 std::vector<TextureInfo> m_textureinfo_cache;
400 // Maps a texture name to an index in the former.
401 std::map<std::string, u32> m_name_to_id;
402 // The two former containers are behind this mutex
403 std::mutex m_textureinfo_cache_mutex;
405 // Queued texture fetches (to be processed by the main thread)
406 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
408 // Textures that have been overwritten with other ones
409 // but can't be deleted because the ITexture* might still be used
410 std::vector<video::ITexture*> m_texture_trash;
412 // Maps image file names to loaded palettes.
413 std::unordered_map<std::string, Palette> m_palettes;
415 // Cached settings needed for making textures from meshes
416 bool m_setting_trilinear_filter;
417 bool m_setting_bilinear_filter;
418 bool m_setting_anisotropic_filter;
421 IWritableTextureSource *createTextureSource()
423 return new TextureSource();
426 TextureSource::TextureSource()
428 m_main_thread = std::this_thread::get_id();
430 // Add a NULL TextureInfo as the first index, named ""
431 m_textureinfo_cache.emplace_back("");
432 m_name_to_id[""] = 0;
434 // Cache some settings
435 // Note: Since this is only done once, the game must be restarted
436 // for these settings to take effect
437 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
438 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
439 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
442 TextureSource::~TextureSource()
444 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
446 unsigned int textures_before = driver->getTextureCount();
448 for (const auto &iter : m_textureinfo_cache) {
451 driver->removeTexture(iter.texture);
453 m_textureinfo_cache.clear();
455 for (auto t : m_texture_trash) {
456 //cleanup trashed texture
457 driver->removeTexture(t);
460 infostream << "~TextureSource() "<< textures_before << "/"
461 << driver->getTextureCount() << std::endl;
464 u32 TextureSource::getTextureId(const std::string &name)
466 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
470 See if texture already exists
472 MutexAutoLock lock(m_textureinfo_cache_mutex);
473 std::map<std::string, u32>::iterator n;
474 n = m_name_to_id.find(name);
475 if (n != m_name_to_id.end())
484 if (std::this_thread::get_id() == m_main_thread) {
485 return generateTexture(name);
489 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
491 // We're gonna ask the result to be put into here
492 static ResultQueue<std::string, u32, u8, u8> result_queue;
494 // Throw a request in
495 m_get_texture_queue.add(name, 0, 0, &result_queue);
499 // Wait result for a second
500 GetResult<std::string, u32, u8, u8>
501 result = result_queue.pop_front(1000);
503 if (result.key == name) {
507 } catch(ItemNotFoundException &e) {
508 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
512 infostream << "getTextureId(): Failed" << std::endl;
517 // Draw an image on top of an another one, using the alpha channel of the
519 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
520 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
522 // Like blit_with_alpha, but only modifies destination pixels that
524 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
525 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
527 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
528 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
529 // color alpha with the destination alpha.
530 // Otherwise, any pixels that are not fully transparent get the color alpha.
531 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
532 const video::SColor &color, int ratio, bool keep_alpha);
534 // paint a texture using the given color
535 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
536 const video::SColor &color);
538 // Apply a mask to an image
539 static void apply_mask(video::IImage *mask, video::IImage *dst,
540 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
542 // Draw or overlay a crack
543 static void draw_crack(video::IImage *crack, video::IImage *dst,
544 bool use_overlay, s32 frame_count, s32 progression,
545 video::IVideoDriver *driver, u8 tiles = 1);
548 void brighten(video::IImage *image);
549 // Parse a transform name
550 u32 parseImageTransform(const std::string& s);
551 // Apply transform to image dimension
552 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
553 // Apply transform to image data
554 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
557 This method generates all the textures
559 u32 TextureSource::generateTexture(const std::string &name)
561 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
563 // Empty name means texture 0
565 infostream<<"generateTexture(): name is empty"<<std::endl;
571 See if texture already exists
573 MutexAutoLock lock(m_textureinfo_cache_mutex);
574 std::map<std::string, u32>::iterator n;
575 n = m_name_to_id.find(name);
576 if (n != m_name_to_id.end()) {
582 Calling only allowed from main thread
584 if (std::this_thread::get_id() != m_main_thread) {
585 errorstream<<"TextureSource::generateTexture() "
586 "called not from main thread"<<std::endl;
590 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
591 sanity_check(driver);
593 video::IImage *img = generateImage(name);
595 video::ITexture *tex = NULL;
599 img = Align2Npot2(img, driver);
601 // Create texture from resulting image
602 tex = driver->addTexture(name.c_str(), img);
603 guiScalingCache(io::path(name.c_str()), driver, img);
608 Add texture to caches (add NULL textures too)
611 MutexAutoLock lock(m_textureinfo_cache_mutex);
613 u32 id = m_textureinfo_cache.size();
614 TextureInfo ti(name, tex);
615 m_textureinfo_cache.push_back(ti);
616 m_name_to_id[name] = id;
621 std::string TextureSource::getTextureName(u32 id)
623 MutexAutoLock lock(m_textureinfo_cache_mutex);
625 if (id >= m_textureinfo_cache.size())
627 errorstream<<"TextureSource::getTextureName(): id="<<id
628 <<" >= m_textureinfo_cache.size()="
629 <<m_textureinfo_cache.size()<<std::endl;
633 return m_textureinfo_cache[id].name;
636 video::ITexture* TextureSource::getTexture(u32 id)
638 MutexAutoLock lock(m_textureinfo_cache_mutex);
640 if (id >= m_textureinfo_cache.size())
643 return m_textureinfo_cache[id].texture;
646 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
648 u32 actual_id = getTextureId(name);
652 return getTexture(actual_id);
655 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
657 return getTexture(name + "^[applyfiltersformesh", id);
660 Palette* TextureSource::getPalette(const std::string &name)
662 // Only the main thread may load images
663 sanity_check(std::this_thread::get_id() == m_main_thread);
668 auto it = m_palettes.find(name);
669 if (it == m_palettes.end()) {
671 video::IImage *img = generateImage(name);
673 warningstream << "TextureSource::getPalette(): palette \"" << name
674 << "\" could not be loaded." << std::endl;
678 u32 w = img->getDimension().Width;
679 u32 h = img->getDimension().Height;
680 // Real area of the image
685 warningstream << "TextureSource::getPalette(): the specified"
686 << " palette image \"" << name << "\" is larger than 256"
687 << " pixels, using the first 256." << std::endl;
689 } else if (256 % area != 0)
690 warningstream << "TextureSource::getPalette(): the "
691 << "specified palette image \"" << name << "\" does not "
692 << "contain power of two pixels." << std::endl;
693 // We stretch the palette so it will fit 256 values
694 // This many param2 values will have the same color
695 u32 step = 256 / area;
696 // For each pixel in the image
697 for (u32 i = 0; i < area; i++) {
698 video::SColor c = img->getPixel(i % w, i / w);
699 // Fill in palette with 'step' colors
700 for (u32 j = 0; j < step; j++)
701 new_palette.push_back(c);
704 // Fill in remaining elements
705 while (new_palette.size() < 256)
706 new_palette.emplace_back(0xFFFFFFFF);
707 m_palettes[name] = new_palette;
708 it = m_palettes.find(name);
710 if (it != m_palettes.end())
711 return &((*it).second);
715 void TextureSource::processQueue()
720 //NOTE this is only thread safe for ONE consumer thread!
721 if (!m_get_texture_queue.empty())
723 GetRequest<std::string, u32, u8, u8>
724 request = m_get_texture_queue.pop();
726 /*infostream<<"TextureSource::processQueue(): "
727 <<"got texture request with "
728 <<"name=\""<<request.key<<"\""
731 m_get_texture_queue.pushResult(request, generateTexture(request.key));
735 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
737 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
739 sanity_check(std::this_thread::get_id() == m_main_thread);
741 m_sourcecache.insert(name, img, true);
742 m_source_image_existence.set(name, true);
745 void TextureSource::rebuildImagesAndTextures()
747 MutexAutoLock lock(m_textureinfo_cache_mutex);
749 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
750 sanity_check(driver);
753 for (TextureInfo &ti : m_textureinfo_cache) {
754 video::IImage *img = generateImage(ti.name);
756 img = Align2Npot2(img, driver);
758 // Create texture from resulting image
759 video::ITexture *t = NULL;
761 t = driver->addTexture(ti.name.c_str(), img);
762 guiScalingCache(io::path(ti.name.c_str()), driver, img);
765 video::ITexture *t_old = ti.texture;
770 m_texture_trash.push_back(t_old);
774 inline static void applyShadeFactor(video::SColor &color, u32 factor)
776 u32 f = core::clamp<u32>(factor, 0, 256);
777 color.setRed(color.getRed() * f / 256);
778 color.setGreen(color.getGreen() * f / 256);
779 color.setBlue(color.getBlue() * f / 256);
782 static video::IImage *createInventoryCubeImage(
783 video::IImage *top, video::IImage *left, video::IImage *right)
785 core::dimension2du size_top = top->getDimension();
786 core::dimension2du size_left = left->getDimension();
787 core::dimension2du size_right = right->getDimension();
789 u32 size = npot2(std::max({
790 size_top.Width, size_top.Height,
791 size_left.Width, size_left.Height,
792 size_right.Width, size_right.Height,
795 // It must be divisible by 4, to let everything work correctly.
796 // But it is a power of 2, so being at least 4 is the same.
797 // And the resulting texture should't be too large as well.
798 size = core::clamp<u32>(size, 4, 64);
800 // With such parameters, the cube fits exactly, touching each image line
801 // from `0` to `cube_size - 1`. (Note that division is exact here).
802 u32 cube_size = 9 * size;
803 u32 offset = size / 2;
805 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
807 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
809 core::dimension2du dim = image->getDimension();
810 video::ECOLOR_FORMAT format = image->getColorFormat();
811 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
812 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
813 image->copyToScaling(scaled);
817 sanity_check(image->getPitch() == 4 * size);
818 return reinterpret_cast<u32 *>(image->lock());
820 auto free_image = [] (video::IImage *image) -> void {
825 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
826 sanity_check(result->getPitch() == 4 * cube_size);
827 result->fill(video::SColor(0x00000000u));
828 u32 *target = reinterpret_cast<u32 *>(result->lock());
830 // Draws single cube face
831 // `shade_factor` is face brightness, in range [0.0, 1.0]
832 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
833 // `offsets` list pixels to be drawn for single source pixel
834 auto draw_image = [=] (video::IImage *image, float shade_factor,
835 s16 xu, s16 xv, s16 x1,
836 s16 yu, s16 yv, s16 y1,
837 std::initializer_list<v2s16> offsets) -> void {
838 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
839 const u32 *source = lock_image(image);
840 for (u16 v = 0; v < size; v++) {
841 for (u16 u = 0; u < size; u++) {
842 video::SColor pixel(*source);
843 applyShadeFactor(pixel, brightness);
844 s16 x = xu * u + xv * v + x1;
845 s16 y = yu * u + yv * v + y1;
846 for (const auto &off : offsets)
847 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
854 draw_image(top, 1.000000f,
855 4, -4, 4 * (size - 1),
858 {2, 0}, {3, 0}, {4, 0}, {5, 0},
859 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
860 {2, 2}, {3, 2}, {4, 2}, {5, 2},
863 draw_image(left, 0.836660f,
868 {0, 1}, {1, 1}, {2, 1}, {3, 1},
869 {0, 2}, {1, 2}, {2, 2}, {3, 2},
870 {0, 3}, {1, 3}, {2, 3}, {3, 3},
871 {0, 4}, {1, 4}, {2, 4}, {3, 4},
875 draw_image(right, 0.670820f,
880 {0, 1}, {1, 1}, {2, 1}, {3, 1},
881 {0, 2}, {1, 2}, {2, 2}, {3, 2},
882 {0, 3}, {1, 3}, {2, 3}, {3, 3},
883 {0, 4}, {1, 4}, {2, 4}, {3, 4},
891 video::IImage* TextureSource::generateImage(const std::string &name)
893 // Get the base image
895 const char separator = '^';
896 const char escape = '\\';
897 const char paren_open = '(';
898 const char paren_close = ')';
900 // Find last separator in the name
901 s32 last_separator_pos = -1;
903 for (s32 i = name.size() - 1; i >= 0; i--) {
904 if (i > 0 && name[i-1] == escape)
908 if (paren_bal == 0) {
909 last_separator_pos = i;
910 i = -1; // break out of loop
914 if (paren_bal == 0) {
915 errorstream << "generateImage(): unbalanced parentheses"
916 << "(extranous '(') while generating texture \""
917 << name << "\"" << std::endl;
930 errorstream << "generateImage(): unbalanced parentheses"
931 << "(missing matching '(') while generating texture \""
932 << name << "\"" << std::endl;
937 video::IImage *baseimg = NULL;
940 If separator was found, make the base image
941 using a recursive call.
943 if (last_separator_pos != -1) {
944 baseimg = generateImage(name.substr(0, last_separator_pos));
948 Parse out the last part of the name of the image and act
952 std::string last_part_of_name = name.substr(last_separator_pos + 1);
955 If this name is enclosed in parentheses, generate it
956 and blit it onto the base image
958 if (last_part_of_name[0] == paren_open
959 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
960 std::string name2 = last_part_of_name.substr(1,
961 last_part_of_name.size() - 2);
962 video::IImage *tmp = generateImage(name2);
964 errorstream << "generateImage(): "
965 "Failed to generate \"" << name2 << "\""
969 core::dimension2d<u32> dim = tmp->getDimension();
971 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
976 } else if (!generateImagePart(last_part_of_name, baseimg)) {
977 // Generate image according to part of name
978 errorstream << "generateImage(): "
979 "Failed to generate \"" << last_part_of_name << "\""
983 // If no resulting image, print a warning
984 if (baseimg == NULL) {
985 errorstream << "generateImage(): baseimg is NULL (attempted to"
986 " create texture \"" << name << "\")" << std::endl;
995 * Check and align image to npot2 if required by hardware
996 * @param image image to check for npot2 alignment
997 * @param driver driver to use for image operations
998 * @return image or copy of image aligned to npot2
1001 inline u16 get_GL_major_version()
1003 const GLubyte *gl_version = glGetString(GL_VERSION);
1004 return (u16) (gl_version[0] - '0');
1007 video::IImage * Align2Npot2(video::IImage * image,
1008 video::IVideoDriver* driver)
1010 if (image == NULL) {
1014 core::dimension2d<u32> dim = image->getDimension();
1016 // Only GLES2 is trusted to correctly report npot support
1017 // Note: we cache the boolean result. GL context will never change on Android.
1018 static const bool hasNPotSupport = get_GL_major_version() > 1 &&
1019 glGetString(GL_EXTENSIONS) &&
1020 strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
1025 unsigned int height = npot2(dim.Height);
1026 unsigned int width = npot2(dim.Width);
1028 if ((dim.Height == height) &&
1029 (dim.Width == width)) {
1033 if (dim.Height > height) {
1037 if (dim.Width > width) {
1041 video::IImage *targetimage =
1042 driver->createImage(video::ECF_A8R8G8B8,
1043 core::dimension2d<u32>(width, height));
1045 if (targetimage != NULL) {
1046 image->copyToScaling(targetimage);
1054 static std::string unescape_string(const std::string &str, const char esc = '\\')
1057 size_t pos = 0, cpos;
1058 out.reserve(str.size());
1060 cpos = str.find_first_of(esc, pos);
1061 if (cpos == std::string::npos) {
1062 out += str.substr(pos);
1065 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1071 bool TextureSource::generateImagePart(std::string part_of_name,
1072 video::IImage *& baseimg)
1074 const char escape = '\\'; // same as in generateImage()
1075 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1076 sanity_check(driver);
1078 // Stuff starting with [ are special commands
1079 if (part_of_name.empty() || part_of_name[0] != '[') {
1080 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1082 image = Align2Npot2(image, driver);
1084 if (image == NULL) {
1085 if (!part_of_name.empty()) {
1087 // Do not create normalmap dummies
1088 if (part_of_name.find("_normal.png") != std::string::npos) {
1089 warningstream << "generateImage(): Could not load normal map \""
1090 << part_of_name << "\"" << std::endl;
1094 errorstream << "generateImage(): Could not load image \""
1095 << part_of_name << "\" while building texture; "
1096 "Creating a dummy image" << std::endl;
1099 // Just create a dummy image
1100 //core::dimension2d<u32> dim(2,2);
1101 core::dimension2d<u32> dim(1,1);
1102 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1103 sanity_check(image != NULL);
1104 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1105 image->setPixel(1,0, video::SColor(255,0,255,0));
1106 image->setPixel(0,1, video::SColor(255,0,0,255));
1107 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1108 image->setPixel(0,0, video::SColor(255,myrand()%256,
1109 myrand()%256,myrand()%256));
1110 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1111 myrand()%256,myrand()%256));
1112 image->setPixel(0,1, video::SColor(255,myrand()%256,
1113 myrand()%256,myrand()%256));
1114 image->setPixel(1,1, video::SColor(255,myrand()%256,
1115 myrand()%256,myrand()%256));*/
1118 // If base image is NULL, load as base.
1119 if (baseimg == NULL)
1121 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1123 Copy it this way to get an alpha channel.
1124 Otherwise images with alpha cannot be blitted on
1125 images that don't have alpha in the original file.
1127 core::dimension2d<u32> dim = image->getDimension();
1128 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1129 image->copyTo(baseimg);
1131 // Else blit on base.
1134 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1135 // Size of the copied area
1136 core::dimension2d<u32> dim = image->getDimension();
1137 //core::dimension2d<u32> dim(16,16);
1138 // Position to copy the blitted to in the base image
1139 core::position2d<s32> pos_to(0,0);
1140 // Position to copy the blitted from in the blitted image
1141 core::position2d<s32> pos_from(0,0);
1143 /*image->copyToWithAlpha(baseimg, pos_to,
1144 core::rect<s32>(pos_from, dim),
1145 video::SColor(255,255,255,255),
1148 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1149 if (dim == dim_dst) {
1150 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1151 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1152 // Upscale overlying image
1153 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1154 createImage(video::ECF_A8R8G8B8, dim_dst);
1155 image->copyToScaling(scaled_image);
1157 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1158 scaled_image->drop();
1160 // Upscale base image
1161 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1162 createImage(video::ECF_A8R8G8B8, dim);
1163 baseimg->copyToScaling(scaled_base);
1165 baseimg = scaled_base;
1167 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1175 // A special texture modification
1177 /*infostream<<"generateImage(): generating special "
1178 <<"modification \""<<part_of_name<<"\""
1184 Adds a cracking texture
1185 N = animation frame count, P = crack progression
1187 if (str_starts_with(part_of_name, "[crack"))
1189 if (baseimg == NULL) {
1190 errorstream<<"generateImagePart(): baseimg == NULL "
1191 <<"for part_of_name=\""<<part_of_name
1192 <<"\", cancelling."<<std::endl;
1196 // Crack image number and overlay option
1197 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1198 bool use_overlay = (part_of_name[6] == 'o');
1199 Strfnd sf(part_of_name);
1201 s32 frame_count = stoi(sf.next(":"));
1202 s32 progression = stoi(sf.next(":"));
1204 // Check whether there is the <tiles> argument, that is,
1205 // whether there are 3 arguments. If so, shift values
1206 // as the first and not the last argument is optional.
1207 auto s = sf.next(":");
1209 tiles = frame_count;
1210 frame_count = progression;
1211 progression = stoi(s);
1214 if (progression >= 0) {
1218 It is an image with a number of cracking stages
1221 video::IImage *img_crack = m_sourcecache.getOrLoad(
1222 "crack_anylength.png");
1225 draw_crack(img_crack, baseimg,
1226 use_overlay, frame_count,
1227 progression, driver, tiles);
1233 [combine:WxH:X,Y=filename:X,Y=filename2
1234 Creates a bigger texture from any amount of smaller ones
1236 else if (str_starts_with(part_of_name, "[combine"))
1238 Strfnd sf(part_of_name);
1240 u32 w0 = stoi(sf.next("x"));
1241 u32 h0 = stoi(sf.next(":"));
1242 core::dimension2d<u32> dim(w0,h0);
1243 if (baseimg == NULL) {
1244 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1245 baseimg->fill(video::SColor(0,0,0,0));
1247 while (!sf.at_end()) {
1248 u32 x = stoi(sf.next(","));
1249 u32 y = stoi(sf.next("="));
1250 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1251 infostream<<"Adding \""<<filename
1252 <<"\" to combined ("<<x<<","<<y<<")"
1254 video::IImage *img = generateImage(filename);
1256 core::dimension2d<u32> dim = img->getDimension();
1257 infostream<<"Size "<<dim.Width
1258 <<"x"<<dim.Height<<std::endl;
1259 core::position2d<s32> pos_base(x, y);
1260 video::IImage *img2 =
1261 driver->createImage(video::ECF_A8R8G8B8, dim);
1264 /*img2->copyToWithAlpha(baseimg, pos_base,
1265 core::rect<s32>(v2s32(0,0), dim),
1266 video::SColor(255,255,255,255),
1268 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1271 errorstream << "generateImagePart(): Failed to load image \""
1272 << filename << "\" for [combine" << std::endl;
1279 else if (str_starts_with(part_of_name, "[brighten"))
1281 if (baseimg == NULL) {
1282 errorstream<<"generateImagePart(): baseimg==NULL "
1283 <<"for part_of_name=\""<<part_of_name
1284 <<"\", cancelling."<<std::endl;
1292 Make image completely opaque.
1293 Used for the leaves texture when in old leaves mode, so
1294 that the transparent parts don't look completely black
1295 when simple alpha channel is used for rendering.
1297 else if (str_starts_with(part_of_name, "[noalpha"))
1299 if (baseimg == NULL){
1300 errorstream<<"generateImagePart(): baseimg==NULL "
1301 <<"for part_of_name=\""<<part_of_name
1302 <<"\", cancelling."<<std::endl;
1306 core::dimension2d<u32> dim = baseimg->getDimension();
1308 // Set alpha to full
1309 for (u32 y=0; y<dim.Height; y++)
1310 for (u32 x=0; x<dim.Width; x++)
1312 video::SColor c = baseimg->getPixel(x,y);
1314 baseimg->setPixel(x,y,c);
1319 Convert one color to transparent.
1321 else if (str_starts_with(part_of_name, "[makealpha:"))
1323 if (baseimg == NULL) {
1324 errorstream<<"generateImagePart(): baseimg == NULL "
1325 <<"for part_of_name=\""<<part_of_name
1326 <<"\", cancelling."<<std::endl;
1330 Strfnd sf(part_of_name.substr(11));
1331 u32 r1 = stoi(sf.next(","));
1332 u32 g1 = stoi(sf.next(","));
1333 u32 b1 = stoi(sf.next(""));
1335 core::dimension2d<u32> dim = baseimg->getDimension();
1337 /*video::IImage *oldbaseimg = baseimg;
1338 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1339 oldbaseimg->copyTo(baseimg);
1340 oldbaseimg->drop();*/
1342 // Set alpha to full
1343 for (u32 y=0; y<dim.Height; y++)
1344 for (u32 x=0; x<dim.Width; x++)
1346 video::SColor c = baseimg->getPixel(x,y);
1348 u32 g = c.getGreen();
1349 u32 b = c.getBlue();
1350 if (!(r == r1 && g == g1 && b == b1))
1353 baseimg->setPixel(x,y,c);
1358 Rotates and/or flips the image.
1360 N can be a number (between 0 and 7) or a transform name.
1361 Rotations are counter-clockwise.
1363 1 R90 rotate by 90 degrees
1364 2 R180 rotate by 180 degrees
1365 3 R270 rotate by 270 degrees
1367 5 FXR90 flip X then rotate by 90 degrees
1369 7 FYR90 flip Y then rotate by 90 degrees
1371 Note: Transform names can be concatenated to produce
1372 their product (applies the first then the second).
1373 The resulting transform will be equivalent to one of the
1374 eight existing ones, though (see: dihedral group).
1376 else if (str_starts_with(part_of_name, "[transform"))
1378 if (baseimg == NULL) {
1379 errorstream<<"generateImagePart(): baseimg == NULL "
1380 <<"for part_of_name=\""<<part_of_name
1381 <<"\", cancelling."<<std::endl;
1385 u32 transform = parseImageTransform(part_of_name.substr(10));
1386 core::dimension2d<u32> dim = imageTransformDimension(
1387 transform, baseimg->getDimension());
1388 video::IImage *image = driver->createImage(
1389 baseimg->getColorFormat(), dim);
1390 sanity_check(image != NULL);
1391 imageTransform(transform, baseimg, image);
1396 [inventorycube{topimage{leftimage{rightimage
1397 In every subimage, replace ^ with &.
1398 Create an "inventory cube".
1399 NOTE: This should be used only on its own.
1400 Example (a grass block (not actually used in game):
1401 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1403 else if (str_starts_with(part_of_name, "[inventorycube"))
1405 if (baseimg != NULL){
1406 errorstream<<"generateImagePart(): baseimg != NULL "
1407 <<"for part_of_name=\""<<part_of_name
1408 <<"\", cancelling."<<std::endl;
1412 str_replace(part_of_name, '&', '^');
1413 Strfnd sf(part_of_name);
1415 std::string imagename_top = sf.next("{");
1416 std::string imagename_left = sf.next("{");
1417 std::string imagename_right = sf.next("{");
1419 // Generate images for the faces of the cube
1420 video::IImage *img_top = generateImage(imagename_top);
1421 video::IImage *img_left = generateImage(imagename_left);
1422 video::IImage *img_right = generateImage(imagename_right);
1424 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1425 errorstream << "generateImagePart(): Failed to create textures"
1426 << " for inventorycube \"" << part_of_name << "\""
1428 baseimg = generateImage(imagename_top);
1432 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1434 // Face images are not needed anymore
1442 [lowpart:percent:filename
1443 Adds the lower part of a texture
1445 else if (str_starts_with(part_of_name, "[lowpart:"))
1447 Strfnd sf(part_of_name);
1449 u32 percent = stoi(sf.next(":"));
1450 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1452 if (baseimg == NULL)
1453 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1454 video::IImage *img = generateImage(filename);
1457 core::dimension2d<u32> dim = img->getDimension();
1458 core::position2d<s32> pos_base(0, 0);
1459 video::IImage *img2 =
1460 driver->createImage(video::ECF_A8R8G8B8, dim);
1463 core::position2d<s32> clippos(0, 0);
1464 clippos.Y = dim.Height * (100-percent) / 100;
1465 core::dimension2d<u32> clipdim = dim;
1466 clipdim.Height = clipdim.Height * percent / 100 + 1;
1467 core::rect<s32> cliprect(clippos, clipdim);
1468 img2->copyToWithAlpha(baseimg, pos_base,
1469 core::rect<s32>(v2s32(0,0), dim),
1470 video::SColor(255,255,255,255),
1477 Crops a frame of a vertical animation.
1478 N = frame count, I = frame index
1480 else if (str_starts_with(part_of_name, "[verticalframe:"))
1482 Strfnd sf(part_of_name);
1484 u32 frame_count = stoi(sf.next(":"));
1485 u32 frame_index = stoi(sf.next(":"));
1487 if (baseimg == NULL){
1488 errorstream<<"generateImagePart(): baseimg != NULL "
1489 <<"for part_of_name=\""<<part_of_name
1490 <<"\", cancelling."<<std::endl;
1494 v2u32 frame_size = baseimg->getDimension();
1495 frame_size.Y /= frame_count;
1497 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1500 errorstream<<"generateImagePart(): Could not create image "
1501 <<"for part_of_name=\""<<part_of_name
1502 <<"\", cancelling."<<std::endl;
1506 // Fill target image with transparency
1507 img->fill(video::SColor(0,0,0,0));
1509 core::dimension2d<u32> dim = frame_size;
1510 core::position2d<s32> pos_dst(0, 0);
1511 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1512 baseimg->copyToWithAlpha(img, pos_dst,
1513 core::rect<s32>(pos_src, dim),
1514 video::SColor(255,255,255,255),
1522 Applies a mask to an image
1524 else if (str_starts_with(part_of_name, "[mask:"))
1526 if (baseimg == NULL) {
1527 errorstream << "generateImage(): baseimg == NULL "
1528 << "for part_of_name=\"" << part_of_name
1529 << "\", cancelling." << std::endl;
1532 Strfnd sf(part_of_name);
1534 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1536 video::IImage *img = generateImage(filename);
1538 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1539 img->getDimension());
1542 errorstream << "generateImage(): Failed to load \""
1543 << filename << "\".";
1548 multiplys a given color to any pixel of an image
1549 color = color as ColorString
1551 else if (str_starts_with(part_of_name, "[multiply:")) {
1552 Strfnd sf(part_of_name);
1554 std::string color_str = sf.next(":");
1556 if (baseimg == NULL) {
1557 errorstream << "generateImagePart(): baseimg != NULL "
1558 << "for part_of_name=\"" << part_of_name
1559 << "\", cancelling." << std::endl;
1563 video::SColor color;
1565 if (!parseColorString(color_str, color, false))
1568 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1572 Overlays image with given color
1573 color = color as ColorString
1575 else if (str_starts_with(part_of_name, "[colorize:"))
1577 Strfnd sf(part_of_name);
1579 std::string color_str = sf.next(":");
1580 std::string ratio_str = sf.next(":");
1582 if (baseimg == NULL) {
1583 errorstream << "generateImagePart(): baseimg != NULL "
1584 << "for part_of_name=\"" << part_of_name
1585 << "\", cancelling." << std::endl;
1589 video::SColor color;
1591 bool keep_alpha = false;
1593 if (!parseColorString(color_str, color, false))
1596 if (is_number(ratio_str))
1597 ratio = mystoi(ratio_str, 0, 255);
1598 else if (ratio_str == "alpha")
1601 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1604 [applyfiltersformesh
1607 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1609 // Apply the "clean transparent" filter, if configured.
1610 if (g_settings->getBool("texture_clean_transparent"))
1611 imageCleanTransparent(baseimg, 127);
1613 /* Upscale textures to user's requested minimum size. This is a trick to make
1614 * filters look as good on low-res textures as on high-res ones, by making
1615 * low-res textures BECOME high-res ones. This is helpful for worlds that
1616 * mix high- and low-res textures, or for mods with least-common-denominator
1617 * textures that don't have the resources to offer high-res alternatives.
1619 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1620 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1622 const core::dimension2d<u32> dim = baseimg->getDimension();
1624 /* Calculate scaling needed to make the shortest texture dimension
1625 * equal to the target minimum. If e.g. this is a vertical frames
1626 * animation, the short dimension will be the real size.
1628 if ((dim.Width == 0) || (dim.Height == 0)) {
1629 errorstream << "generateImagePart(): Illegal 0 dimension "
1630 << "for part_of_name=\""<< part_of_name
1631 << "\", cancelling." << std::endl;
1634 u32 xscale = scaleto / dim.Width;
1635 u32 yscale = scaleto / dim.Height;
1636 u32 scale = (xscale > yscale) ? xscale : yscale;
1638 // Never downscale; only scale up by 2x or more.
1640 u32 w = scale * dim.Width;
1641 u32 h = scale * dim.Height;
1642 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1643 video::IImage *newimg = driver->createImage(
1644 baseimg->getColorFormat(), newdim);
1645 baseimg->copyToScaling(newimg);
1653 Resizes the base image to the given dimensions
1655 else if (str_starts_with(part_of_name, "[resize"))
1657 if (baseimg == NULL) {
1658 errorstream << "generateImagePart(): baseimg == NULL "
1659 << "for part_of_name=\""<< part_of_name
1660 << "\", cancelling." << std::endl;
1664 Strfnd sf(part_of_name);
1666 u32 width = stoi(sf.next("x"));
1667 u32 height = stoi(sf.next(""));
1668 core::dimension2d<u32> dim(width, height);
1670 video::IImage *image = RenderingEngine::get_video_driver()->
1671 createImage(video::ECF_A8R8G8B8, dim);
1672 baseimg->copyToScaling(image);
1678 Makes the base image transparent according to the given ratio.
1679 R must be between 0 and 255.
1680 0 means totally transparent.
1681 255 means totally opaque.
1683 else if (str_starts_with(part_of_name, "[opacity:")) {
1684 if (baseimg == NULL) {
1685 errorstream << "generateImagePart(): baseimg == NULL "
1686 << "for part_of_name=\"" << part_of_name
1687 << "\", cancelling." << std::endl;
1691 Strfnd sf(part_of_name);
1694 u32 ratio = mystoi(sf.next(""), 0, 255);
1696 core::dimension2d<u32> dim = baseimg->getDimension();
1698 for (u32 y = 0; y < dim.Height; y++)
1699 for (u32 x = 0; x < dim.Width; x++)
1701 video::SColor c = baseimg->getPixel(x, y);
1702 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1703 baseimg->setPixel(x, y, c);
1708 Inverts the given channels of the base image.
1709 Mode may contain the characters "r", "g", "b", "a".
1710 Only the channels that are mentioned in the mode string
1713 else if (str_starts_with(part_of_name, "[invert:")) {
1714 if (baseimg == NULL) {
1715 errorstream << "generateImagePart(): baseimg == NULL "
1716 << "for part_of_name=\"" << part_of_name
1717 << "\", cancelling." << std::endl;
1721 Strfnd sf(part_of_name);
1724 std::string mode = sf.next("");
1726 if (mode.find('a') != std::string::npos)
1727 mask |= 0xff000000UL;
1728 if (mode.find('r') != std::string::npos)
1729 mask |= 0x00ff0000UL;
1730 if (mode.find('g') != std::string::npos)
1731 mask |= 0x0000ff00UL;
1732 if (mode.find('b') != std::string::npos)
1733 mask |= 0x000000ffUL;
1735 core::dimension2d<u32> dim = baseimg->getDimension();
1737 for (u32 y = 0; y < dim.Height; y++)
1738 for (u32 x = 0; x < dim.Width; x++)
1740 video::SColor c = baseimg->getPixel(x, y);
1742 baseimg->setPixel(x, y, c);
1747 Retrieves a tile at position X,Y (in tiles)
1748 from the base image it assumes to be a
1749 tilesheet with dimensions W,H (in tiles).
1751 else if (part_of_name.substr(0,7) == "[sheet:") {
1752 if (baseimg == NULL) {
1753 errorstream << "generateImagePart(): baseimg != NULL "
1754 << "for part_of_name=\"" << part_of_name
1755 << "\", cancelling." << std::endl;
1759 Strfnd sf(part_of_name);
1761 u32 w0 = stoi(sf.next("x"));
1762 u32 h0 = stoi(sf.next(":"));
1763 u32 x0 = stoi(sf.next(","));
1764 u32 y0 = stoi(sf.next(":"));
1766 core::dimension2d<u32> img_dim = baseimg->getDimension();
1767 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1769 video::IImage *img = driver->createImage(
1770 video::ECF_A8R8G8B8, tile_dim);
1772 errorstream << "generateImagePart(): Could not create image "
1773 << "for part_of_name=\"" << part_of_name
1774 << "\", cancelling." << std::endl;
1778 img->fill(video::SColor(0,0,0,0));
1779 v2u32 vdim(tile_dim);
1780 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1781 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1782 video::SColor(255,255,255,255), NULL);
1790 errorstream << "generateImagePart(): Invalid "
1791 " modification: \"" << part_of_name << "\"" << std::endl;
1799 Draw an image on top of an another one, using the alpha channel of the
1802 This exists because IImage::copyToWithAlpha() doesn't seem to always
1805 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1806 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1808 for (u32 y0=0; y0<size.Y; y0++)
1809 for (u32 x0=0; x0<size.X; x0++)
1811 s32 src_x = src_pos.X + x0;
1812 s32 src_y = src_pos.Y + y0;
1813 s32 dst_x = dst_pos.X + x0;
1814 s32 dst_y = dst_pos.Y + y0;
1815 video::SColor src_c = src->getPixel(src_x, src_y);
1816 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1817 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1818 dst->setPixel(dst_x, dst_y, dst_c);
1823 Draw an image on top of an another one, using the alpha channel of the
1824 source image; only modify fully opaque pixels in destinaion
1826 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1827 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1829 for (u32 y0=0; y0<size.Y; y0++)
1830 for (u32 x0=0; x0<size.X; x0++)
1832 s32 src_x = src_pos.X + x0;
1833 s32 src_y = src_pos.Y + y0;
1834 s32 dst_x = dst_pos.X + x0;
1835 s32 dst_y = dst_pos.Y + y0;
1836 video::SColor src_c = src->getPixel(src_x, src_y);
1837 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1838 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1840 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1841 dst->setPixel(dst_x, dst_y, dst_c);
1846 // This function has been disabled because it is currently unused.
1847 // Feel free to re-enable if you find it handy.
1850 Draw an image on top of an another one, using the specified ratio
1851 modify all partially-opaque pixels in the destination.
1853 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1854 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1856 for (u32 y0 = 0; y0 < size.Y; y0++)
1857 for (u32 x0 = 0; x0 < size.X; x0++)
1859 s32 src_x = src_pos.X + x0;
1860 s32 src_y = src_pos.Y + y0;
1861 s32 dst_x = dst_pos.X + x0;
1862 s32 dst_y = dst_pos.Y + y0;
1863 video::SColor src_c = src->getPixel(src_x, src_y);
1864 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1865 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1868 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1870 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1871 dst->setPixel(dst_x, dst_y, dst_c);
1878 Apply color to destination
1880 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1881 const video::SColor &color, int ratio, bool keep_alpha)
1883 u32 alpha = color.getAlpha();
1884 video::SColor dst_c;
1885 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1886 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1888 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1889 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1890 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1891 if (dst_alpha > 0) {
1892 dst_c.setAlpha(dst_alpha * alpha / 255);
1893 dst->setPixel(x, y, dst_c);
1896 } else { // replace the color including the alpha
1897 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1898 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1899 if (dst->getPixel(x, y).getAlpha() > 0)
1900 dst->setPixel(x, y, color);
1902 } else { // interpolate between the color and destination
1903 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1904 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1905 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1906 dst_c = dst->getPixel(x, y);
1907 if (dst_c.getAlpha() > 0) {
1908 dst_c = color.getInterpolated(dst_c, interp);
1909 dst->setPixel(x, y, dst_c);
1916 Apply color to destination
1918 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1919 const video::SColor &color)
1921 video::SColor dst_c;
1923 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1924 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1925 dst_c = dst->getPixel(x, y);
1928 (dst_c.getRed() * color.getRed()) / 255,
1929 (dst_c.getGreen() * color.getGreen()) / 255,
1930 (dst_c.getBlue() * color.getBlue()) / 255
1932 dst->setPixel(x, y, dst_c);
1937 Apply mask to destination
1939 static void apply_mask(video::IImage *mask, video::IImage *dst,
1940 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1942 for (u32 y0 = 0; y0 < size.Y; y0++) {
1943 for (u32 x0 = 0; x0 < size.X; x0++) {
1944 s32 mask_x = x0 + mask_pos.X;
1945 s32 mask_y = y0 + mask_pos.Y;
1946 s32 dst_x = x0 + dst_pos.X;
1947 s32 dst_y = y0 + dst_pos.Y;
1948 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1949 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1950 dst_c.color &= mask_c.color;
1951 dst->setPixel(dst_x, dst_y, dst_c);
1956 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
1957 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
1959 core::dimension2d<u32> strip_size = crack->getDimension();
1960 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
1961 core::dimension2d<u32> tile_size(size / tiles);
1962 s32 frame_count = strip_size.Height / strip_size.Width;
1963 if (frame_index >= frame_count)
1964 frame_index = frame_count - 1;
1965 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
1966 video::IImage *result = nullptr;
1968 // extract crack frame
1969 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
1972 if (tile_size == frame_size) {
1973 crack->copyTo(crack_tile, v2s32(0, 0), frame);
1975 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
1977 goto exit__has_tile;
1978 crack->copyTo(crack_frame, v2s32(0, 0), frame);
1979 crack_frame->copyToScaling(crack_tile);
1980 crack_frame->drop();
1986 result = driver->createImage(video::ECF_A8R8G8B8, size);
1988 goto exit__has_tile;
1990 for (u8 i = 0; i < tiles; i++)
1991 for (u8 j = 0; j < tiles; j++)
1992 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
1999 static void draw_crack(video::IImage *crack, video::IImage *dst,
2000 bool use_overlay, s32 frame_count, s32 progression,
2001 video::IVideoDriver *driver, u8 tiles)
2003 // Dimension of destination image
2004 core::dimension2d<u32> dim_dst = dst->getDimension();
2005 // Limit frame_count
2006 if (frame_count > (s32) dim_dst.Height)
2007 frame_count = dim_dst.Height;
2008 if (frame_count < 1)
2010 // Dimension of the scaled crack stage,
2011 // which is the same as the dimension of a single destination frame
2012 core::dimension2d<u32> frame_size(
2014 dim_dst.Height / frame_count
2016 video::IImage *crack_scaled = create_crack_image(crack, progression,
2017 frame_size, tiles, driver);
2021 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2022 for (s32 i = 0; i < frame_count; ++i) {
2023 v2s32 dst_pos(0, frame_size.Height * i);
2024 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2027 crack_scaled->drop();
2030 void brighten(video::IImage *image)
2035 core::dimension2d<u32> dim = image->getDimension();
2037 for (u32 y=0; y<dim.Height; y++)
2038 for (u32 x=0; x<dim.Width; x++)
2040 video::SColor c = image->getPixel(x,y);
2041 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2042 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2043 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2044 image->setPixel(x,y,c);
2048 u32 parseImageTransform(const std::string& s)
2050 int total_transform = 0;
2052 std::string transform_names[8];
2053 transform_names[0] = "i";
2054 transform_names[1] = "r90";
2055 transform_names[2] = "r180";
2056 transform_names[3] = "r270";
2057 transform_names[4] = "fx";
2058 transform_names[6] = "fy";
2060 std::size_t pos = 0;
2061 while(pos < s.size())
2064 for (int i = 0; i <= 7; ++i)
2066 const std::string &name_i = transform_names[i];
2068 if (s[pos] == ('0' + i))
2075 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2077 pos += name_i.size();
2084 // Multiply total_transform and transform in the group D4
2087 new_total = (transform + total_transform) % 4;
2089 new_total = (transform - total_transform + 8) % 4;
2090 if ((transform >= 4) ^ (total_transform >= 4))
2093 total_transform = new_total;
2095 return total_transform;
2098 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2100 if (transform % 2 == 0)
2103 return core::dimension2d<u32>(dim.Height, dim.Width);
2106 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2108 if (src == NULL || dst == NULL)
2111 core::dimension2d<u32> dstdim = dst->getDimension();
2114 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2115 assert(transform <= 7);
2118 Compute the transformation from source coordinates (sx,sy)
2119 to destination coordinates (dx,dy).
2123 if (transform == 0) // identity
2124 sxn = 0, syn = 2; // sx = dx, sy = dy
2125 else if (transform == 1) // rotate by 90 degrees ccw
2126 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2127 else if (transform == 2) // rotate by 180 degrees
2128 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2129 else if (transform == 3) // rotate by 270 degrees ccw
2130 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2131 else if (transform == 4) // flip x
2132 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2133 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2134 sxn = 2, syn = 0; // sx = dy, sy = dx
2135 else if (transform == 6) // flip y
2136 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2137 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2138 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2140 for (u32 dy=0; dy<dstdim.Height; dy++)
2141 for (u32 dx=0; dx<dstdim.Width; dx++)
2143 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2144 u32 sx = entries[sxn];
2145 u32 sy = entries[syn];
2146 video::SColor c = src->getPixel(sx,sy);
2147 dst->setPixel(dx,dy,c);
2151 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2153 if (isKnownSourceImage("override_normal.png"))
2154 return getTexture("override_normal.png");
2155 std::string fname_base = name;
2156 static const char *normal_ext = "_normal.png";
2157 static const u32 normal_ext_size = strlen(normal_ext);
2158 size_t pos = fname_base.find('.');
2159 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2160 if (isKnownSourceImage(fname_normal)) {
2161 // look for image extension and replace it
2163 while ((i = fname_base.find('.', i)) != std::string::npos) {
2164 fname_base.replace(i, 4, normal_ext);
2165 i += normal_ext_size;
2167 return getTexture(fname_base);
2172 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2174 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2175 video::SColor c(0, 0, 0, 0);
2176 video::ITexture *texture = getTexture(name);
2177 video::IImage *image = driver->createImage(texture,
2178 core::position2d<s32>(0, 0),
2179 texture->getOriginalSize());
2184 core::dimension2d<u32> dim = image->getDimension();
2187 step = dim.Width / 16;
2188 for (u16 x = 0; x < dim.Width; x += step) {
2189 for (u16 y = 0; y < dim.Width; y += step) {
2190 c = image->getPixel(x,y);
2191 if (c.getAlpha() > 0) {
2201 c.setRed(tR / total);
2202 c.setGreen(tG / total);
2203 c.setBlue(tB / total);
2210 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2212 std::string tname = "__shaderFlagsTexture";
2213 tname += normalmap_present ? "1" : "0";
2215 if (isKnownSourceImage(tname)) {
2216 return getTexture(tname);
2219 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2220 video::IImage *flags_image = driver->createImage(
2221 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2222 sanity_check(flags_image != NULL);
2223 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2224 flags_image->setPixel(0, 0, c);
2225 insertSourceImage(tname, flags_image);
2226 flags_image->drop();
2227 return getTexture(tname);
2231 std::vector<std::string> getTextureDirs()
2233 return fs::GetRecursiveDirs(g_settings->get("texture_path"));