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 <IrrCompileConfig.h>
25 #include "util/string.h"
26 #include "util/container.h"
27 #include "util/thread.h"
32 #include "util/strfnd.h"
33 #include "imagefilters.h"
34 #include "guiscalingfilter.h"
35 #include "renderingengine.h"
38 A cache from texture name to texture path
40 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
43 Replaces the filename extension.
45 std::string image = "a/image.png"
46 replace_ext(image, "jpg")
47 -> image = "a/image.jpg"
48 Returns true on success.
50 static bool replace_ext(std::string &path, const char *ext)
54 // Find place of last dot, fail if \ or / found.
56 for (s32 i=path.size()-1; i>=0; i--)
64 if (path[i] == '\\' || path[i] == '/')
67 // If not found, return an empty string
70 // Else make the new path
71 path = path.substr(0, last_dot_i+1) + ext;
76 Find out the full path of an image by trying different filename
81 std::string getImagePath(std::string path)
83 // A NULL-ended list of possible image extensions
84 const char *extensions[] = {
85 "png", "jpg", "bmp", "tga",
86 "pcx", "ppm", "psd", "wal", "rgb",
89 // If there is no extension, add one
90 if (removeStringEnd(path, extensions).empty())
92 // Check paths until something is found to exist
93 const char **ext = extensions;
95 bool r = replace_ext(path, *ext);
98 if (fs::PathExists(path))
101 while((++ext) != NULL);
107 Gets the path to a texture by first checking if the texture exists
108 in texture_path and if not, using the data path.
110 Checks all supported extensions by replacing the original extension.
112 If not found, returns "".
114 Utilizes a thread-safe cache.
116 std::string getTexturePath(const std::string &filename, bool *is_base_pack)
118 std::string fullpath;
120 // This can set a wrong value on cached textures, but is irrelevant because
121 // is_base_pack is only passed when initializing the textures the first time
123 *is_base_pack = false;
127 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
132 Check from texture_path
134 for (const auto &path : getTextureDirs()) {
135 std::string testpath = path + DIR_DELIM;
136 testpath.append(filename);
137 // Check all filename extensions. Returns "" if not found.
138 fullpath = getImagePath(testpath);
139 if (!fullpath.empty())
144 Check from default data directory
146 if (fullpath.empty())
148 std::string base_path = porting::path_share + DIR_DELIM + "textures"
149 + DIR_DELIM + "base" + DIR_DELIM + "pack";
150 std::string testpath = base_path + DIR_DELIM + filename;
151 // Check all filename extensions. Returns "" if not found.
152 fullpath = getImagePath(testpath);
153 if (is_base_pack && !fullpath.empty())
154 *is_base_pack = true;
157 // Add to cache (also an empty result is cached)
158 g_texturename_to_path_cache.set(filename, fullpath);
164 void clearTextureNameCache()
166 g_texturename_to_path_cache.clear();
170 Stores internal information about a texture.
176 video::ITexture *texture;
179 const std::string &name_,
180 video::ITexture *texture_=NULL
189 SourceImageCache: A cache used for storing source images.
192 class SourceImageCache
195 ~SourceImageCache() {
196 for (auto &m_image : m_images) {
197 m_image.second->drop();
201 void insert(const std::string &name, video::IImage *img, bool prefer_local)
203 assert(img); // Pre-condition
205 std::map<std::string, video::IImage*>::iterator n;
206 n = m_images.find(name);
207 if (n != m_images.end()){
212 video::IImage* toadd = img;
213 bool need_to_grab = true;
215 // Try to use local texture instead if asked to
218 std::string path = getTexturePath(name, &is_base_pack);
220 if (!path.empty() && !is_base_pack) {
221 video::IImage *img2 = RenderingEngine::get_video_driver()->
222 createImageFromFile(path.c_str());
225 need_to_grab = false;
232 m_images[name] = toadd;
234 video::IImage* get(const std::string &name)
236 std::map<std::string, video::IImage*>::iterator n;
237 n = m_images.find(name);
238 if (n != m_images.end())
242 // Primarily fetches from cache, secondarily tries to read from filesystem
243 video::IImage *getOrLoad(const std::string &name)
245 std::map<std::string, video::IImage*>::iterator n;
246 n = m_images.find(name);
247 if (n != m_images.end()){
248 n->second->grab(); // Grab for caller
251 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
252 std::string path = getTexturePath(name);
254 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
255 <<name<<"\""<<std::endl;
258 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
260 video::IImage *img = driver->createImageFromFile(path.c_str());
263 m_images[name] = img;
264 img->grab(); // Grab for caller
269 std::map<std::string, video::IImage*> m_images;
276 class TextureSource : public IWritableTextureSource
280 virtual ~TextureSource();
284 Now, assume a texture with the id 1 exists, and has the name
285 "stone.png^mineral1".
286 Then a random thread calls getTextureId for a texture called
287 "stone.png^mineral1^crack0".
288 ...Now, WTF should happen? Well:
289 - getTextureId strips off stuff recursively from the end until
290 the remaining part is found, or nothing is left when
291 something is stripped out
293 But it is slow to search for textures by names and modify them
295 - ContentFeatures is made to contain ids for the basic plain
297 - Crack textures can be slow by themselves, but the framework
301 - Assume a texture with the id 1 exists, and has the name
302 "stone.png^mineral_coal.png".
303 - Now getNodeTile() stumbles upon a node which uses
304 texture id 1, and determines that MATERIAL_FLAG_CRACK
305 must be applied to the tile
306 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
307 has received the current crack level 0 from the client. It
308 finds out the name of the texture with getTextureName(1),
309 appends "^crack0" to it and gets a new texture id with
310 getTextureId("stone.png^mineral_coal.png^crack0").
315 Gets a texture id from cache or
316 - if main thread, generates the texture, adds to cache and returns id.
317 - if other thread, adds to request queue and waits for main thread.
319 The id 0 points to a NULL texture. It is returned in case of error.
321 u32 getTextureId(const std::string &name);
323 // Finds out the name of a cached texture.
324 std::string getTextureName(u32 id);
327 If texture specified by the name pointed by the id doesn't
328 exist, create it, then return the cached texture.
330 Can be called from any thread. If called from some other thread
331 and not found in cache, the call is queued to the main thread
334 video::ITexture* getTexture(u32 id);
336 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
339 Get a texture specifically intended for mesh
340 application, i.e. not HUD, compositing, or other 2D
341 use. This texture may be a different size and may
342 have had additional filters applied.
344 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
346 virtual Palette* getPalette(const std::string &name);
348 bool isKnownSourceImage(const std::string &name)
350 bool is_known = false;
351 bool cache_found = m_source_image_existence.get(name, &is_known);
354 // Not found in cache; find out if a local file exists
355 is_known = (!getTexturePath(name).empty());
356 m_source_image_existence.set(name, is_known);
360 // Processes queued texture requests from other threads.
361 // Shall be called from the main thread.
364 // Insert an image into the cache without touching the filesystem.
365 // Shall be called from the main thread.
366 void insertSourceImage(const std::string &name, video::IImage *img);
368 // Rebuild images and textures from the current set of source images
369 // Shall be called from the main thread.
370 void rebuildImagesAndTextures();
372 video::ITexture* getNormalTexture(const std::string &name);
373 video::SColor getTextureAverageColor(const std::string &name);
374 video::ITexture *getShaderFlagsTexture(bool normamap_present);
378 // The id of the thread that is allowed to use irrlicht directly
379 std::thread::id m_main_thread;
381 // Cache of source images
382 // This should be only accessed from the main thread
383 SourceImageCache m_sourcecache;
385 // Generate a texture
386 u32 generateTexture(const std::string &name);
388 // Generate image based on a string like "stone.png" or "[crack:1:0".
389 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
390 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
392 /*! Generates an image from a full string like
393 * "stone.png^mineral_coal.png^[crack:1:0".
394 * Shall be called from the main thread.
395 * The returned Image should be dropped.
397 video::IImage* generateImage(const std::string &name);
399 // Thread-safe cache of what source images are known (true = known)
400 MutexedMap<std::string, bool> m_source_image_existence;
402 // A texture id is index in this array.
403 // The first position contains a NULL texture.
404 std::vector<TextureInfo> m_textureinfo_cache;
405 // Maps a texture name to an index in the former.
406 std::map<std::string, u32> m_name_to_id;
407 // The two former containers are behind this mutex
408 std::mutex m_textureinfo_cache_mutex;
410 // Queued texture fetches (to be processed by the main thread)
411 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
413 // Textures that have been overwritten with other ones
414 // but can't be deleted because the ITexture* might still be used
415 std::vector<video::ITexture*> m_texture_trash;
417 // Maps image file names to loaded palettes.
418 std::unordered_map<std::string, Palette> m_palettes;
420 // Cached settings needed for making textures from meshes
421 bool m_setting_mipmap;
422 bool m_setting_trilinear_filter;
423 bool m_setting_bilinear_filter;
426 IWritableTextureSource *createTextureSource()
428 return new TextureSource();
431 TextureSource::TextureSource()
433 m_main_thread = std::this_thread::get_id();
435 // Add a NULL TextureInfo as the first index, named ""
436 m_textureinfo_cache.emplace_back("");
437 m_name_to_id[""] = 0;
439 // Cache some settings
440 // Note: Since this is only done once, the game must be restarted
441 // for these settings to take effect
442 m_setting_mipmap = g_settings->getBool("mip_map");
443 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
444 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
447 TextureSource::~TextureSource()
449 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
451 unsigned int textures_before = driver->getTextureCount();
453 for (const auto &iter : m_textureinfo_cache) {
456 driver->removeTexture(iter.texture);
458 m_textureinfo_cache.clear();
460 for (auto t : m_texture_trash) {
461 //cleanup trashed texture
462 driver->removeTexture(t);
465 infostream << "~TextureSource() before cleanup: "<< textures_before
466 << " after: " << driver->getTextureCount() << std::endl;
469 u32 TextureSource::getTextureId(const std::string &name)
471 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
475 See if texture already exists
477 MutexAutoLock lock(m_textureinfo_cache_mutex);
478 std::map<std::string, u32>::iterator n;
479 n = m_name_to_id.find(name);
480 if (n != m_name_to_id.end())
489 if (std::this_thread::get_id() == m_main_thread) {
490 return generateTexture(name);
494 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
496 // We're gonna ask the result to be put into here
497 static ResultQueue<std::string, u32, u8, u8> result_queue;
499 // Throw a request in
500 m_get_texture_queue.add(name, 0, 0, &result_queue);
504 // Wait result for a second
505 GetResult<std::string, u32, u8, u8>
506 result = result_queue.pop_front(1000);
508 if (result.key == name) {
512 } catch(ItemNotFoundException &e) {
513 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
517 infostream << "getTextureId(): Failed" << std::endl;
522 // Draw an image on top of an another one, using the alpha channel of the
524 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
525 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
527 // Like blit_with_alpha, but only modifies destination pixels that
529 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
530 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
532 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
533 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
534 // color alpha with the destination alpha.
535 // Otherwise, any pixels that are not fully transparent get the color alpha.
536 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
537 const video::SColor &color, int ratio, bool keep_alpha);
539 // paint a texture using the given color
540 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
541 const video::SColor &color);
543 // Apply a mask to an image
544 static void apply_mask(video::IImage *mask, video::IImage *dst,
545 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
547 // Draw or overlay a crack
548 static void draw_crack(video::IImage *crack, video::IImage *dst,
549 bool use_overlay, s32 frame_count, s32 progression,
550 video::IVideoDriver *driver, u8 tiles = 1);
553 void brighten(video::IImage *image);
554 // Parse a transform name
555 u32 parseImageTransform(const std::string& s);
556 // Apply transform to image dimension
557 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
558 // Apply transform to image data
559 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
562 This method generates all the textures
564 u32 TextureSource::generateTexture(const std::string &name)
566 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
568 // Empty name means texture 0
570 infostream<<"generateTexture(): name is empty"<<std::endl;
576 See if texture already exists
578 MutexAutoLock lock(m_textureinfo_cache_mutex);
579 std::map<std::string, u32>::iterator n;
580 n = m_name_to_id.find(name);
581 if (n != m_name_to_id.end()) {
587 Calling only allowed from main thread
589 if (std::this_thread::get_id() != m_main_thread) {
590 errorstream<<"TextureSource::generateTexture() "
591 "called not from main thread"<<std::endl;
595 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
596 sanity_check(driver);
598 video::IImage *img = generateImage(name);
600 video::ITexture *tex = NULL;
604 img = Align2Npot2(img, driver);
606 // Create texture from resulting image
607 tex = driver->addTexture(name.c_str(), img);
608 guiScalingCache(io::path(name.c_str()), driver, img);
613 Add texture to caches (add NULL textures too)
616 MutexAutoLock lock(m_textureinfo_cache_mutex);
618 u32 id = m_textureinfo_cache.size();
619 TextureInfo ti(name, tex);
620 m_textureinfo_cache.push_back(ti);
621 m_name_to_id[name] = id;
626 std::string TextureSource::getTextureName(u32 id)
628 MutexAutoLock lock(m_textureinfo_cache_mutex);
630 if (id >= m_textureinfo_cache.size())
632 errorstream<<"TextureSource::getTextureName(): id="<<id
633 <<" >= m_textureinfo_cache.size()="
634 <<m_textureinfo_cache.size()<<std::endl;
638 return m_textureinfo_cache[id].name;
641 video::ITexture* TextureSource::getTexture(u32 id)
643 MutexAutoLock lock(m_textureinfo_cache_mutex);
645 if (id >= m_textureinfo_cache.size())
648 return m_textureinfo_cache[id].texture;
651 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
653 u32 actual_id = getTextureId(name);
657 return getTexture(actual_id);
660 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
662 static thread_local bool filter_needed =
663 g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
664 ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
665 g_settings->getS32("texture_min_size") > 1);
666 // Avoid duplicating texture if it won't actually change
668 return getTexture(name + "^[applyfiltersformesh", id);
669 return getTexture(name, id);
672 Palette* TextureSource::getPalette(const std::string &name)
674 // Only the main thread may load images
675 sanity_check(std::this_thread::get_id() == m_main_thread);
680 auto it = m_palettes.find(name);
681 if (it == m_palettes.end()) {
683 video::IImage *img = generateImage(name);
685 warningstream << "TextureSource::getPalette(): palette \"" << name
686 << "\" could not be loaded." << std::endl;
690 u32 w = img->getDimension().Width;
691 u32 h = img->getDimension().Height;
692 // Real area of the image
697 warningstream << "TextureSource::getPalette(): the specified"
698 << " palette image \"" << name << "\" is larger than 256"
699 << " pixels, using the first 256." << std::endl;
701 } else if (256 % area != 0)
702 warningstream << "TextureSource::getPalette(): the "
703 << "specified palette image \"" << name << "\" does not "
704 << "contain power of two pixels." << std::endl;
705 // We stretch the palette so it will fit 256 values
706 // This many param2 values will have the same color
707 u32 step = 256 / area;
708 // For each pixel in the image
709 for (u32 i = 0; i < area; i++) {
710 video::SColor c = img->getPixel(i % w, i / w);
711 // Fill in palette with 'step' colors
712 for (u32 j = 0; j < step; j++)
713 new_palette.push_back(c);
716 // Fill in remaining elements
717 while (new_palette.size() < 256)
718 new_palette.emplace_back(0xFFFFFFFF);
719 m_palettes[name] = new_palette;
720 it = m_palettes.find(name);
722 if (it != m_palettes.end())
723 return &((*it).second);
727 void TextureSource::processQueue()
732 //NOTE this is only thread safe for ONE consumer thread!
733 if (!m_get_texture_queue.empty())
735 GetRequest<std::string, u32, u8, u8>
736 request = m_get_texture_queue.pop();
738 /*infostream<<"TextureSource::processQueue(): "
739 <<"got texture request with "
740 <<"name=\""<<request.key<<"\""
743 m_get_texture_queue.pushResult(request, generateTexture(request.key));
747 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
749 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
751 sanity_check(std::this_thread::get_id() == m_main_thread);
753 m_sourcecache.insert(name, img, true);
754 m_source_image_existence.set(name, true);
757 void TextureSource::rebuildImagesAndTextures()
759 MutexAutoLock lock(m_textureinfo_cache_mutex);
761 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
762 sanity_check(driver);
764 infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
765 << " textures" << std::endl;
768 for (TextureInfo &ti : m_textureinfo_cache) {
769 video::IImage *img = generateImage(ti.name);
771 img = Align2Npot2(img, driver);
773 // Create texture from resulting image
774 video::ITexture *t = NULL;
776 t = driver->addTexture(ti.name.c_str(), img);
777 guiScalingCache(io::path(ti.name.c_str()), driver, img);
780 video::ITexture *t_old = ti.texture;
785 m_texture_trash.push_back(t_old);
789 inline static void applyShadeFactor(video::SColor &color, u32 factor)
791 u32 f = core::clamp<u32>(factor, 0, 256);
792 color.setRed(color.getRed() * f / 256);
793 color.setGreen(color.getGreen() * f / 256);
794 color.setBlue(color.getBlue() * f / 256);
797 static video::IImage *createInventoryCubeImage(
798 video::IImage *top, video::IImage *left, video::IImage *right)
800 core::dimension2du size_top = top->getDimension();
801 core::dimension2du size_left = left->getDimension();
802 core::dimension2du size_right = right->getDimension();
804 u32 size = npot2(std::max({
805 size_top.Width, size_top.Height,
806 size_left.Width, size_left.Height,
807 size_right.Width, size_right.Height,
810 // It must be divisible by 4, to let everything work correctly.
811 // But it is a power of 2, so being at least 4 is the same.
812 // And the resulting texture should't be too large as well.
813 size = core::clamp<u32>(size, 4, 64);
815 // With such parameters, the cube fits exactly, touching each image line
816 // from `0` to `cube_size - 1`. (Note that division is exact here).
817 u32 cube_size = 9 * size;
818 u32 offset = size / 2;
820 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
822 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
824 core::dimension2du dim = image->getDimension();
825 video::ECOLOR_FORMAT format = image->getColorFormat();
826 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
827 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
828 image->copyToScaling(scaled);
832 sanity_check(image->getPitch() == 4 * size);
833 return reinterpret_cast<u32 *>(image->getData());
835 auto free_image = [] (video::IImage *image) -> void {
839 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
840 sanity_check(result->getPitch() == 4 * cube_size);
841 result->fill(video::SColor(0x00000000u));
842 u32 *target = reinterpret_cast<u32 *>(result->getData());
844 // Draws single cube face
845 // `shade_factor` is face brightness, in range [0.0, 1.0]
846 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
847 // `offsets` list pixels to be drawn for single source pixel
848 auto draw_image = [=] (video::IImage *image, float shade_factor,
849 s16 xu, s16 xv, s16 x1,
850 s16 yu, s16 yv, s16 y1,
851 std::initializer_list<v2s16> offsets) -> void {
852 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
853 const u32 *source = lock_image(image);
854 for (u16 v = 0; v < size; v++) {
855 for (u16 u = 0; u < size; u++) {
856 video::SColor pixel(*source);
857 applyShadeFactor(pixel, brightness);
858 s16 x = xu * u + xv * v + x1;
859 s16 y = yu * u + yv * v + y1;
860 for (const auto &off : offsets)
861 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
868 draw_image(top, 1.000000f,
869 4, -4, 4 * (size - 1),
872 {2, 0}, {3, 0}, {4, 0}, {5, 0},
873 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
874 {2, 2}, {3, 2}, {4, 2}, {5, 2},
877 draw_image(left, 0.836660f,
882 {0, 1}, {1, 1}, {2, 1}, {3, 1},
883 {0, 2}, {1, 2}, {2, 2}, {3, 2},
884 {0, 3}, {1, 3}, {2, 3}, {3, 3},
885 {0, 4}, {1, 4}, {2, 4}, {3, 4},
889 draw_image(right, 0.670820f,
894 {0, 1}, {1, 1}, {2, 1}, {3, 1},
895 {0, 2}, {1, 2}, {2, 2}, {3, 2},
896 {0, 3}, {1, 3}, {2, 3}, {3, 3},
897 {0, 4}, {1, 4}, {2, 4}, {3, 4},
904 video::IImage* TextureSource::generateImage(const std::string &name)
906 // Get the base image
908 const char separator = '^';
909 const char escape = '\\';
910 const char paren_open = '(';
911 const char paren_close = ')';
913 // Find last separator in the name
914 s32 last_separator_pos = -1;
916 for (s32 i = name.size() - 1; i >= 0; i--) {
917 if (i > 0 && name[i-1] == escape)
921 if (paren_bal == 0) {
922 last_separator_pos = i;
923 i = -1; // break out of loop
927 if (paren_bal == 0) {
928 errorstream << "generateImage(): unbalanced parentheses"
929 << "(extranous '(') while generating texture \""
930 << name << "\"" << std::endl;
943 errorstream << "generateImage(): unbalanced parentheses"
944 << "(missing matching '(') while generating texture \""
945 << name << "\"" << std::endl;
950 video::IImage *baseimg = NULL;
953 If separator was found, make the base image
954 using a recursive call.
956 if (last_separator_pos != -1) {
957 baseimg = generateImage(name.substr(0, last_separator_pos));
961 Parse out the last part of the name of the image and act
965 std::string last_part_of_name = name.substr(last_separator_pos + 1);
968 If this name is enclosed in parentheses, generate it
969 and blit it onto the base image
971 if (last_part_of_name[0] == paren_open
972 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
973 std::string name2 = last_part_of_name.substr(1,
974 last_part_of_name.size() - 2);
975 video::IImage *tmp = generateImage(name2);
977 errorstream << "generateImage(): "
978 "Failed to generate \"" << name2 << "\""
982 core::dimension2d<u32> dim = tmp->getDimension();
984 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
989 } else if (!generateImagePart(last_part_of_name, baseimg)) {
990 // Generate image according to part of name
991 errorstream << "generateImage(): "
992 "Failed to generate \"" << last_part_of_name << "\""
996 // If no resulting image, print a warning
997 if (baseimg == NULL) {
998 errorstream << "generateImage(): baseimg is NULL (attempted to"
999 " create texture \"" << name << "\")" << std::endl;
1008 * Check and align image to npot2 if required by hardware
1009 * @param image image to check for npot2 alignment
1010 * @param driver driver to use for image operations
1011 * @return image or copy of image aligned to npot2
1013 video::IImage *Align2Npot2(video::IImage *image,
1014 video::IVideoDriver *driver)
1019 if (driver->queryFeature(video::EVDF_TEXTURE_NPOT))
1022 core::dimension2d<u32> dim = image->getDimension();
1023 unsigned int height = npot2(dim.Height);
1024 unsigned int width = npot2(dim.Width);
1026 if (dim.Height == height && dim.Width == width)
1029 if (dim.Height > height)
1031 if (dim.Width > width)
1034 video::IImage *targetimage =
1035 driver->createImage(video::ECF_A8R8G8B8,
1036 core::dimension2d<u32>(width, height));
1038 if (targetimage != NULL)
1039 image->copyToScaling(targetimage);
1046 static std::string unescape_string(const std::string &str, const char esc = '\\')
1049 size_t pos = 0, cpos;
1050 out.reserve(str.size());
1052 cpos = str.find_first_of(esc, pos);
1053 if (cpos == std::string::npos) {
1054 out += str.substr(pos);
1057 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1063 bool TextureSource::generateImagePart(std::string part_of_name,
1064 video::IImage *& baseimg)
1066 const char escape = '\\'; // same as in generateImage()
1067 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1068 sanity_check(driver);
1070 // Stuff starting with [ are special commands
1071 if (part_of_name.empty() || part_of_name[0] != '[') {
1072 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1074 image = Align2Npot2(image, driver);
1076 if (image == NULL) {
1077 if (!part_of_name.empty()) {
1079 // Do not create normalmap dummies
1080 if (part_of_name.find("_normal.png") != std::string::npos) {
1081 warningstream << "generateImage(): Could not load normal map \""
1082 << part_of_name << "\"" << std::endl;
1086 errorstream << "generateImage(): Could not load image \""
1087 << part_of_name << "\" while building texture; "
1088 "Creating a dummy image" << std::endl;
1091 // Just create a dummy image
1092 //core::dimension2d<u32> dim(2,2);
1093 core::dimension2d<u32> dim(1,1);
1094 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1095 sanity_check(image != NULL);
1096 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1097 image->setPixel(1,0, video::SColor(255,0,255,0));
1098 image->setPixel(0,1, video::SColor(255,0,0,255));
1099 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1100 image->setPixel(0,0, video::SColor(255,myrand()%256,
1101 myrand()%256,myrand()%256));
1102 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1103 myrand()%256,myrand()%256));
1104 image->setPixel(0,1, video::SColor(255,myrand()%256,
1105 myrand()%256,myrand()%256));
1106 image->setPixel(1,1, video::SColor(255,myrand()%256,
1107 myrand()%256,myrand()%256));*/
1110 // If base image is NULL, load as base.
1111 if (baseimg == NULL)
1113 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1115 Copy it this way to get an alpha channel.
1116 Otherwise images with alpha cannot be blitted on
1117 images that don't have alpha in the original file.
1119 core::dimension2d<u32> dim = image->getDimension();
1120 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1121 image->copyTo(baseimg);
1123 // Else blit on base.
1126 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1127 // Size of the copied area
1128 core::dimension2d<u32> dim = image->getDimension();
1129 //core::dimension2d<u32> dim(16,16);
1130 // Position to copy the blitted to in the base image
1131 core::position2d<s32> pos_to(0,0);
1132 // Position to copy the blitted from in the blitted image
1133 core::position2d<s32> pos_from(0,0);
1135 /*image->copyToWithAlpha(baseimg, pos_to,
1136 core::rect<s32>(pos_from, dim),
1137 video::SColor(255,255,255,255),
1140 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1141 if (dim == dim_dst) {
1142 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1143 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1144 // Upscale overlying image
1145 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1146 createImage(video::ECF_A8R8G8B8, dim_dst);
1147 image->copyToScaling(scaled_image);
1149 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1150 scaled_image->drop();
1152 // Upscale base image
1153 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1154 createImage(video::ECF_A8R8G8B8, dim);
1155 baseimg->copyToScaling(scaled_base);
1157 baseimg = scaled_base;
1159 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1167 // A special texture modification
1169 /*infostream<<"generateImage(): generating special "
1170 <<"modification \""<<part_of_name<<"\""
1176 Adds a cracking texture
1177 N = animation frame count, P = crack progression
1179 if (str_starts_with(part_of_name, "[crack"))
1181 if (baseimg == NULL) {
1182 errorstream<<"generateImagePart(): baseimg == NULL "
1183 <<"for part_of_name=\""<<part_of_name
1184 <<"\", cancelling."<<std::endl;
1188 // Crack image number and overlay option
1189 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1190 bool use_overlay = (part_of_name[6] == 'o');
1191 Strfnd sf(part_of_name);
1193 s32 frame_count = stoi(sf.next(":"));
1194 s32 progression = stoi(sf.next(":"));
1196 // Check whether there is the <tiles> argument, that is,
1197 // whether there are 3 arguments. If so, shift values
1198 // as the first and not the last argument is optional.
1199 auto s = sf.next(":");
1201 tiles = frame_count;
1202 frame_count = progression;
1203 progression = stoi(s);
1206 if (progression >= 0) {
1210 It is an image with a number of cracking stages
1213 video::IImage *img_crack = m_sourcecache.getOrLoad(
1214 "crack_anylength.png");
1217 draw_crack(img_crack, baseimg,
1218 use_overlay, frame_count,
1219 progression, driver, tiles);
1225 [combine:WxH:X,Y=filename:X,Y=filename2
1226 Creates a bigger texture from any amount of smaller ones
1228 else if (str_starts_with(part_of_name, "[combine"))
1230 Strfnd sf(part_of_name);
1232 u32 w0 = stoi(sf.next("x"));
1233 u32 h0 = stoi(sf.next(":"));
1234 core::dimension2d<u32> dim(w0,h0);
1235 if (baseimg == NULL) {
1236 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1237 baseimg->fill(video::SColor(0,0,0,0));
1239 while (!sf.at_end()) {
1240 u32 x = stoi(sf.next(","));
1241 u32 y = stoi(sf.next("="));
1242 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1243 infostream<<"Adding \""<<filename
1244 <<"\" to combined ("<<x<<","<<y<<")"
1246 video::IImage *img = generateImage(filename);
1248 core::dimension2d<u32> dim = img->getDimension();
1249 core::position2d<s32> pos_base(x, y);
1250 video::IImage *img2 =
1251 driver->createImage(video::ECF_A8R8G8B8, dim);
1254 /*img2->copyToWithAlpha(baseimg, pos_base,
1255 core::rect<s32>(v2s32(0,0), dim),
1256 video::SColor(255,255,255,255),
1258 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1261 errorstream << "generateImagePart(): Failed to load image \""
1262 << filename << "\" for [combine" << std::endl;
1269 else if (str_starts_with(part_of_name, "[brighten"))
1271 if (baseimg == NULL) {
1272 errorstream<<"generateImagePart(): baseimg==NULL "
1273 <<"for part_of_name=\""<<part_of_name
1274 <<"\", cancelling."<<std::endl;
1282 Make image completely opaque.
1283 Used for the leaves texture when in old leaves mode, so
1284 that the transparent parts don't look completely black
1285 when simple alpha channel is used for rendering.
1287 else if (str_starts_with(part_of_name, "[noalpha"))
1289 if (baseimg == NULL){
1290 errorstream<<"generateImagePart(): baseimg==NULL "
1291 <<"for part_of_name=\""<<part_of_name
1292 <<"\", cancelling."<<std::endl;
1296 core::dimension2d<u32> dim = baseimg->getDimension();
1298 // Set alpha to full
1299 for (u32 y=0; y<dim.Height; y++)
1300 for (u32 x=0; x<dim.Width; x++)
1302 video::SColor c = baseimg->getPixel(x,y);
1304 baseimg->setPixel(x,y,c);
1309 Convert one color to transparent.
1311 else if (str_starts_with(part_of_name, "[makealpha:"))
1313 if (baseimg == NULL) {
1314 errorstream<<"generateImagePart(): baseimg == NULL "
1315 <<"for part_of_name=\""<<part_of_name
1316 <<"\", cancelling."<<std::endl;
1320 Strfnd sf(part_of_name.substr(11));
1321 u32 r1 = stoi(sf.next(","));
1322 u32 g1 = stoi(sf.next(","));
1323 u32 b1 = stoi(sf.next(""));
1325 core::dimension2d<u32> dim = baseimg->getDimension();
1327 /*video::IImage *oldbaseimg = baseimg;
1328 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1329 oldbaseimg->copyTo(baseimg);
1330 oldbaseimg->drop();*/
1332 // Set alpha to full
1333 for (u32 y=0; y<dim.Height; y++)
1334 for (u32 x=0; x<dim.Width; x++)
1336 video::SColor c = baseimg->getPixel(x,y);
1338 u32 g = c.getGreen();
1339 u32 b = c.getBlue();
1340 if (!(r == r1 && g == g1 && b == b1))
1343 baseimg->setPixel(x,y,c);
1348 Rotates and/or flips the image.
1350 N can be a number (between 0 and 7) or a transform name.
1351 Rotations are counter-clockwise.
1353 1 R90 rotate by 90 degrees
1354 2 R180 rotate by 180 degrees
1355 3 R270 rotate by 270 degrees
1357 5 FXR90 flip X then rotate by 90 degrees
1359 7 FYR90 flip Y then rotate by 90 degrees
1361 Note: Transform names can be concatenated to produce
1362 their product (applies the first then the second).
1363 The resulting transform will be equivalent to one of the
1364 eight existing ones, though (see: dihedral group).
1366 else if (str_starts_with(part_of_name, "[transform"))
1368 if (baseimg == NULL) {
1369 errorstream<<"generateImagePart(): baseimg == NULL "
1370 <<"for part_of_name=\""<<part_of_name
1371 <<"\", cancelling."<<std::endl;
1375 u32 transform = parseImageTransform(part_of_name.substr(10));
1376 core::dimension2d<u32> dim = imageTransformDimension(
1377 transform, baseimg->getDimension());
1378 video::IImage *image = driver->createImage(
1379 baseimg->getColorFormat(), dim);
1380 sanity_check(image != NULL);
1381 imageTransform(transform, baseimg, image);
1386 [inventorycube{topimage{leftimage{rightimage
1387 In every subimage, replace ^ with &.
1388 Create an "inventory cube".
1389 NOTE: This should be used only on its own.
1390 Example (a grass block (not actually used in game):
1391 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1393 else if (str_starts_with(part_of_name, "[inventorycube"))
1395 if (baseimg != NULL){
1396 errorstream<<"generateImagePart(): baseimg != NULL "
1397 <<"for part_of_name=\""<<part_of_name
1398 <<"\", cancelling."<<std::endl;
1402 str_replace(part_of_name, '&', '^');
1403 Strfnd sf(part_of_name);
1405 std::string imagename_top = sf.next("{");
1406 std::string imagename_left = sf.next("{");
1407 std::string imagename_right = sf.next("{");
1409 // Generate images for the faces of the cube
1410 video::IImage *img_top = generateImage(imagename_top);
1411 video::IImage *img_left = generateImage(imagename_left);
1412 video::IImage *img_right = generateImage(imagename_right);
1414 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1415 errorstream << "generateImagePart(): Failed to create textures"
1416 << " for inventorycube \"" << part_of_name << "\""
1418 baseimg = generateImage(imagename_top);
1422 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1424 // Face images are not needed anymore
1432 [lowpart:percent:filename
1433 Adds the lower part of a texture
1435 else if (str_starts_with(part_of_name, "[lowpart:"))
1437 Strfnd sf(part_of_name);
1439 u32 percent = stoi(sf.next(":"));
1440 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1442 if (baseimg == NULL)
1443 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1444 video::IImage *img = generateImage(filename);
1447 core::dimension2d<u32> dim = img->getDimension();
1448 core::position2d<s32> pos_base(0, 0);
1449 video::IImage *img2 =
1450 driver->createImage(video::ECF_A8R8G8B8, dim);
1453 core::position2d<s32> clippos(0, 0);
1454 clippos.Y = dim.Height * (100-percent) / 100;
1455 core::dimension2d<u32> clipdim = dim;
1456 clipdim.Height = clipdim.Height * percent / 100 + 1;
1457 core::rect<s32> cliprect(clippos, clipdim);
1458 img2->copyToWithAlpha(baseimg, pos_base,
1459 core::rect<s32>(v2s32(0,0), dim),
1460 video::SColor(255,255,255,255),
1467 Crops a frame of a vertical animation.
1468 N = frame count, I = frame index
1470 else if (str_starts_with(part_of_name, "[verticalframe:"))
1472 Strfnd sf(part_of_name);
1474 u32 frame_count = stoi(sf.next(":"));
1475 u32 frame_index = stoi(sf.next(":"));
1477 if (baseimg == NULL){
1478 errorstream<<"generateImagePart(): baseimg != NULL "
1479 <<"for part_of_name=\""<<part_of_name
1480 <<"\", cancelling."<<std::endl;
1484 v2u32 frame_size = baseimg->getDimension();
1485 frame_size.Y /= frame_count;
1487 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1490 errorstream<<"generateImagePart(): Could not create image "
1491 <<"for part_of_name=\""<<part_of_name
1492 <<"\", cancelling."<<std::endl;
1496 // Fill target image with transparency
1497 img->fill(video::SColor(0,0,0,0));
1499 core::dimension2d<u32> dim = frame_size;
1500 core::position2d<s32> pos_dst(0, 0);
1501 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1502 baseimg->copyToWithAlpha(img, pos_dst,
1503 core::rect<s32>(pos_src, dim),
1504 video::SColor(255,255,255,255),
1512 Applies a mask to an image
1514 else if (str_starts_with(part_of_name, "[mask:"))
1516 if (baseimg == NULL) {
1517 errorstream << "generateImage(): baseimg == NULL "
1518 << "for part_of_name=\"" << part_of_name
1519 << "\", cancelling." << std::endl;
1522 Strfnd sf(part_of_name);
1524 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1526 video::IImage *img = generateImage(filename);
1528 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1529 img->getDimension());
1532 errorstream << "generateImage(): Failed to load \""
1533 << filename << "\".";
1538 multiplys a given color to any pixel of an image
1539 color = color as ColorString
1541 else if (str_starts_with(part_of_name, "[multiply:")) {
1542 Strfnd sf(part_of_name);
1544 std::string color_str = sf.next(":");
1546 if (baseimg == NULL) {
1547 errorstream << "generateImagePart(): baseimg != NULL "
1548 << "for part_of_name=\"" << part_of_name
1549 << "\", cancelling." << std::endl;
1553 video::SColor color;
1555 if (!parseColorString(color_str, color, false))
1558 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1562 Overlays image with given color
1563 color = color as ColorString
1565 else if (str_starts_with(part_of_name, "[colorize:"))
1567 Strfnd sf(part_of_name);
1569 std::string color_str = sf.next(":");
1570 std::string ratio_str = sf.next(":");
1572 if (baseimg == NULL) {
1573 errorstream << "generateImagePart(): baseimg != NULL "
1574 << "for part_of_name=\"" << part_of_name
1575 << "\", cancelling." << std::endl;
1579 video::SColor color;
1581 bool keep_alpha = false;
1583 if (!parseColorString(color_str, color, false))
1586 if (is_number(ratio_str))
1587 ratio = mystoi(ratio_str, 0, 255);
1588 else if (ratio_str == "alpha")
1591 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1594 [applyfiltersformesh
1597 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1599 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1603 errorstream << "generateImagePart(): baseimg == NULL "
1604 << "for part_of_name=\"" << part_of_name
1605 << "\", cancelling." << std::endl;
1609 // Apply the "clean transparent" filter, if needed
1610 if (m_setting_mipmap || 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 Calculate the color of a single pixel drawn on top of another pixel.
1801 This is a little more complicated than just video::SColor::getInterpolated
1802 because getInterpolated does not handle alpha correctly. For example, a
1803 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1804 pixel with alpha=160, while getInterpolated would yield alpha=96.
1806 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1808 if (dst_c.getAlpha() == 0)
1810 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1811 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1812 src_c.getAlpha() * ratio / (255 * 255));
1817 Draw an image on top of an another one, using the alpha channel of the
1820 This exists because IImage::copyToWithAlpha() doesn't seem to always
1823 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1824 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1826 for (u32 y0=0; y0<size.Y; y0++)
1827 for (u32 x0=0; x0<size.X; x0++)
1829 s32 src_x = src_pos.X + x0;
1830 s32 src_y = src_pos.Y + y0;
1831 s32 dst_x = dst_pos.X + x0;
1832 s32 dst_y = dst_pos.Y + y0;
1833 video::SColor src_c = src->getPixel(src_x, src_y);
1834 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1835 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1836 dst->setPixel(dst_x, dst_y, dst_c);
1841 Draw an image on top of an another one, using the alpha channel of the
1842 source image; only modify fully opaque pixels in destinaion
1844 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1845 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1847 for (u32 y0=0; y0<size.Y; y0++)
1848 for (u32 x0=0; x0<size.X; x0++)
1850 s32 src_x = src_pos.X + x0;
1851 s32 src_y = src_pos.Y + y0;
1852 s32 dst_x = dst_pos.X + x0;
1853 s32 dst_y = dst_pos.Y + y0;
1854 video::SColor src_c = src->getPixel(src_x, src_y);
1855 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1856 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1858 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1859 dst->setPixel(dst_x, dst_y, dst_c);
1864 // This function has been disabled because it is currently unused.
1865 // Feel free to re-enable if you find it handy.
1868 Draw an image on top of an another one, using the specified ratio
1869 modify all partially-opaque pixels in the destination.
1871 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1872 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1874 for (u32 y0 = 0; y0 < size.Y; y0++)
1875 for (u32 x0 = 0; x0 < size.X; x0++)
1877 s32 src_x = src_pos.X + x0;
1878 s32 src_y = src_pos.Y + y0;
1879 s32 dst_x = dst_pos.X + x0;
1880 s32 dst_y = dst_pos.Y + y0;
1881 video::SColor src_c = src->getPixel(src_x, src_y);
1882 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1883 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1886 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1888 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1889 dst->setPixel(dst_x, dst_y, dst_c);
1896 Apply color to destination
1898 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1899 const video::SColor &color, int ratio, bool keep_alpha)
1901 u32 alpha = color.getAlpha();
1902 video::SColor dst_c;
1903 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1904 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1906 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1907 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1908 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1909 if (dst_alpha > 0) {
1910 dst_c.setAlpha(dst_alpha * alpha / 255);
1911 dst->setPixel(x, y, dst_c);
1914 } else { // replace the color including the alpha
1915 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1916 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1917 if (dst->getPixel(x, y).getAlpha() > 0)
1918 dst->setPixel(x, y, color);
1920 } else { // interpolate between the color and destination
1921 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1922 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1923 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1924 dst_c = dst->getPixel(x, y);
1925 if (dst_c.getAlpha() > 0) {
1926 dst_c = color.getInterpolated(dst_c, interp);
1927 dst->setPixel(x, y, dst_c);
1934 Apply color to destination
1936 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1937 const video::SColor &color)
1939 video::SColor dst_c;
1941 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1942 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1943 dst_c = dst->getPixel(x, y);
1946 (dst_c.getRed() * color.getRed()) / 255,
1947 (dst_c.getGreen() * color.getGreen()) / 255,
1948 (dst_c.getBlue() * color.getBlue()) / 255
1950 dst->setPixel(x, y, dst_c);
1955 Apply mask to destination
1957 static void apply_mask(video::IImage *mask, video::IImage *dst,
1958 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1960 for (u32 y0 = 0; y0 < size.Y; y0++) {
1961 for (u32 x0 = 0; x0 < size.X; x0++) {
1962 s32 mask_x = x0 + mask_pos.X;
1963 s32 mask_y = y0 + mask_pos.Y;
1964 s32 dst_x = x0 + dst_pos.X;
1965 s32 dst_y = y0 + dst_pos.Y;
1966 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1967 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1968 dst_c.color &= mask_c.color;
1969 dst->setPixel(dst_x, dst_y, dst_c);
1974 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
1975 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
1977 core::dimension2d<u32> strip_size = crack->getDimension();
1978 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
1979 core::dimension2d<u32> tile_size(size / tiles);
1980 s32 frame_count = strip_size.Height / strip_size.Width;
1981 if (frame_index >= frame_count)
1982 frame_index = frame_count - 1;
1983 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
1984 video::IImage *result = nullptr;
1986 // extract crack frame
1987 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
1990 if (tile_size == frame_size) {
1991 crack->copyTo(crack_tile, v2s32(0, 0), frame);
1993 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
1995 goto exit__has_tile;
1996 crack->copyTo(crack_frame, v2s32(0, 0), frame);
1997 crack_frame->copyToScaling(crack_tile);
1998 crack_frame->drop();
2004 result = driver->createImage(video::ECF_A8R8G8B8, size);
2006 goto exit__has_tile;
2008 for (u8 i = 0; i < tiles; i++)
2009 for (u8 j = 0; j < tiles; j++)
2010 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2017 static void draw_crack(video::IImage *crack, video::IImage *dst,
2018 bool use_overlay, s32 frame_count, s32 progression,
2019 video::IVideoDriver *driver, u8 tiles)
2021 // Dimension of destination image
2022 core::dimension2d<u32> dim_dst = dst->getDimension();
2023 // Limit frame_count
2024 if (frame_count > (s32) dim_dst.Height)
2025 frame_count = dim_dst.Height;
2026 if (frame_count < 1)
2028 // Dimension of the scaled crack stage,
2029 // which is the same as the dimension of a single destination frame
2030 core::dimension2d<u32> frame_size(
2032 dim_dst.Height / frame_count
2034 video::IImage *crack_scaled = create_crack_image(crack, progression,
2035 frame_size, tiles, driver);
2039 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2040 for (s32 i = 0; i < frame_count; ++i) {
2041 v2s32 dst_pos(0, frame_size.Height * i);
2042 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2045 crack_scaled->drop();
2048 void brighten(video::IImage *image)
2053 core::dimension2d<u32> dim = image->getDimension();
2055 for (u32 y=0; y<dim.Height; y++)
2056 for (u32 x=0; x<dim.Width; x++)
2058 video::SColor c = image->getPixel(x,y);
2059 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2060 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2061 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2062 image->setPixel(x,y,c);
2066 u32 parseImageTransform(const std::string& s)
2068 int total_transform = 0;
2070 std::string transform_names[8];
2071 transform_names[0] = "i";
2072 transform_names[1] = "r90";
2073 transform_names[2] = "r180";
2074 transform_names[3] = "r270";
2075 transform_names[4] = "fx";
2076 transform_names[6] = "fy";
2078 std::size_t pos = 0;
2079 while(pos < s.size())
2082 for (int i = 0; i <= 7; ++i)
2084 const std::string &name_i = transform_names[i];
2086 if (s[pos] == ('0' + i))
2093 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2095 pos += name_i.size();
2102 // Multiply total_transform and transform in the group D4
2105 new_total = (transform + total_transform) % 4;
2107 new_total = (transform - total_transform + 8) % 4;
2108 if ((transform >= 4) ^ (total_transform >= 4))
2111 total_transform = new_total;
2113 return total_transform;
2116 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2118 if (transform % 2 == 0)
2121 return core::dimension2d<u32>(dim.Height, dim.Width);
2124 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2126 if (src == NULL || dst == NULL)
2129 core::dimension2d<u32> dstdim = dst->getDimension();
2132 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2133 assert(transform <= 7);
2136 Compute the transformation from source coordinates (sx,sy)
2137 to destination coordinates (dx,dy).
2141 if (transform == 0) // identity
2142 sxn = 0, syn = 2; // sx = dx, sy = dy
2143 else if (transform == 1) // rotate by 90 degrees ccw
2144 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2145 else if (transform == 2) // rotate by 180 degrees
2146 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2147 else if (transform == 3) // rotate by 270 degrees ccw
2148 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2149 else if (transform == 4) // flip x
2150 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2151 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2152 sxn = 2, syn = 0; // sx = dy, sy = dx
2153 else if (transform == 6) // flip y
2154 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2155 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2156 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2158 for (u32 dy=0; dy<dstdim.Height; dy++)
2159 for (u32 dx=0; dx<dstdim.Width; dx++)
2161 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2162 u32 sx = entries[sxn];
2163 u32 sy = entries[syn];
2164 video::SColor c = src->getPixel(sx,sy);
2165 dst->setPixel(dx,dy,c);
2169 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2171 if (isKnownSourceImage("override_normal.png"))
2172 return getTexture("override_normal.png");
2173 std::string fname_base = name;
2174 static const char *normal_ext = "_normal.png";
2175 static const u32 normal_ext_size = strlen(normal_ext);
2176 size_t pos = fname_base.find('.');
2177 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2178 if (isKnownSourceImage(fname_normal)) {
2179 // look for image extension and replace it
2181 while ((i = fname_base.find('.', i)) != std::string::npos) {
2182 fname_base.replace(i, 4, normal_ext);
2183 i += normal_ext_size;
2185 return getTexture(fname_base);
2190 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2192 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2193 video::SColor c(0, 0, 0, 0);
2194 video::ITexture *texture = getTexture(name);
2197 video::IImage *image = driver->createImage(texture,
2198 core::position2d<s32>(0, 0),
2199 texture->getOriginalSize());
2207 core::dimension2d<u32> dim = image->getDimension();
2210 step = dim.Width / 16;
2211 for (u16 x = 0; x < dim.Width; x += step) {
2212 for (u16 y = 0; y < dim.Width; y += step) {
2213 c = image->getPixel(x,y);
2214 if (c.getAlpha() > 0) {
2224 c.setRed(tR / total);
2225 c.setGreen(tG / total);
2226 c.setBlue(tB / total);
2233 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2235 std::string tname = "__shaderFlagsTexture";
2236 tname += normalmap_present ? "1" : "0";
2238 if (isKnownSourceImage(tname)) {
2239 return getTexture(tname);
2242 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2243 video::IImage *flags_image = driver->createImage(
2244 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2245 sanity_check(flags_image != NULL);
2246 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2247 flags_image->setPixel(0, 0, c);
2248 insertSourceImage(tname, flags_image);
2249 flags_image->drop();
2250 return getTexture(tname);
2254 std::vector<std::string> getTextureDirs()
2256 return fs::GetRecursiveDirs(g_settings->get("texture_path"));