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"
35 #include "util/base64.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[] = { "png", "jpg", "bmp", "tga", NULL };
85 // If there is no extension, assume PNG
86 if (removeStringEnd(path, extensions).empty())
88 // Check paths until something is found to exist
89 const char **ext = extensions;
91 bool r = replace_ext(path, *ext);
94 if (fs::PathExists(path))
97 while((++ext) != NULL);
103 Gets the path to a texture by first checking if the texture exists
104 in texture_path and if not, using the data path.
106 Checks all supported extensions by replacing the original extension.
108 If not found, returns "".
110 Utilizes a thread-safe cache.
112 std::string getTexturePath(const std::string &filename, bool *is_base_pack)
114 std::string fullpath;
116 // This can set a wrong value on cached textures, but is irrelevant because
117 // is_base_pack is only passed when initializing the textures the first time
119 *is_base_pack = false;
123 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
128 Check from texture_path
130 for (const auto &path : getTextureDirs()) {
131 std::string testpath = path + DIR_DELIM;
132 testpath.append(filename);
133 // Check all filename extensions. Returns "" if not found.
134 fullpath = getImagePath(testpath);
135 if (!fullpath.empty())
140 Check from default data directory
142 if (fullpath.empty())
144 std::string base_path = porting::path_share + DIR_DELIM + "textures"
145 + DIR_DELIM + "base" + DIR_DELIM + "pack";
146 std::string testpath = base_path + DIR_DELIM + filename;
147 // Check all filename extensions. Returns "" if not found.
148 fullpath = getImagePath(testpath);
149 if (is_base_pack && !fullpath.empty())
150 *is_base_pack = true;
153 // Add to cache (also an empty result is cached)
154 g_texturename_to_path_cache.set(filename, fullpath);
160 void clearTextureNameCache()
162 g_texturename_to_path_cache.clear();
166 Stores internal information about a texture.
172 video::ITexture *texture;
173 std::set<std::string> sourceImages;
176 const std::string &name_,
177 video::ITexture *texture_=NULL
185 const std::string &name_,
186 video::ITexture *texture_,
187 std::set<std::string> &sourceImages_
191 sourceImages(sourceImages_)
197 SourceImageCache: A cache used for storing source images.
200 class SourceImageCache
203 ~SourceImageCache() {
204 for (auto &m_image : m_images) {
205 m_image.second->drop();
209 void insert(const std::string &name, video::IImage *img, bool prefer_local)
211 assert(img); // Pre-condition
213 std::map<std::string, video::IImage*>::iterator n;
214 n = m_images.find(name);
215 if (n != m_images.end()){
220 video::IImage* toadd = img;
221 bool need_to_grab = true;
223 // Try to use local texture instead if asked to
226 std::string path = getTexturePath(name, &is_base_pack);
228 if (!path.empty() && !is_base_pack) {
229 video::IImage *img2 = RenderingEngine::get_video_driver()->
230 createImageFromFile(path.c_str());
233 need_to_grab = false;
240 m_images[name] = toadd;
242 video::IImage* get(const std::string &name)
244 std::map<std::string, video::IImage*>::iterator n;
245 n = m_images.find(name);
246 if (n != m_images.end())
250 // Primarily fetches from cache, secondarily tries to read from filesystem
251 video::IImage *getOrLoad(const std::string &name)
253 std::map<std::string, video::IImage*>::iterator n;
254 n = m_images.find(name);
255 if (n != m_images.end()){
256 n->second->grab(); // Grab for caller
259 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
260 std::string path = getTexturePath(name);
262 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
263 <<name<<"\""<<std::endl;
266 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
268 video::IImage *img = driver->createImageFromFile(path.c_str());
271 m_images[name] = img;
272 img->grab(); // Grab for caller
277 std::map<std::string, video::IImage*> m_images;
284 class TextureSource : public IWritableTextureSource
288 virtual ~TextureSource();
292 Now, assume a texture with the id 1 exists, and has the name
293 "stone.png^mineral1".
294 Then a random thread calls getTextureId for a texture called
295 "stone.png^mineral1^crack0".
296 ...Now, WTF should happen? Well:
297 - getTextureId strips off stuff recursively from the end until
298 the remaining part is found, or nothing is left when
299 something is stripped out
301 But it is slow to search for textures by names and modify them
303 - ContentFeatures is made to contain ids for the basic plain
305 - Crack textures can be slow by themselves, but the framework
309 - Assume a texture with the id 1 exists, and has the name
310 "stone.png^mineral_coal.png".
311 - Now getNodeTile() stumbles upon a node which uses
312 texture id 1, and determines that MATERIAL_FLAG_CRACK
313 must be applied to the tile
314 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
315 has received the current crack level 0 from the client. It
316 finds out the name of the texture with getTextureName(1),
317 appends "^crack0" to it and gets a new texture id with
318 getTextureId("stone.png^mineral_coal.png^crack0").
323 Gets a texture id from cache or
324 - if main thread, generates the texture, adds to cache and returns id.
325 - if other thread, adds to request queue and waits for main thread.
327 The id 0 points to a NULL texture. It is returned in case of error.
329 u32 getTextureId(const std::string &name);
331 // Finds out the name of a cached texture.
332 std::string getTextureName(u32 id);
335 If texture specified by the name pointed by the id doesn't
336 exist, create it, then return the cached texture.
338 Can be called from any thread. If called from some other thread
339 and not found in cache, the call is queued to the main thread
342 video::ITexture* getTexture(u32 id);
344 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
347 Get a texture specifically intended for mesh
348 application, i.e. not HUD, compositing, or other 2D
349 use. This texture may be a different size and may
350 have had additional filters applied.
352 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
354 virtual Palette* getPalette(const std::string &name);
356 bool isKnownSourceImage(const std::string &name)
358 bool is_known = false;
359 bool cache_found = m_source_image_existence.get(name, &is_known);
362 // Not found in cache; find out if a local file exists
363 is_known = (!getTexturePath(name).empty());
364 m_source_image_existence.set(name, is_known);
368 // Processes queued texture requests from other threads.
369 // Shall be called from the main thread.
372 // Insert an image into the cache without touching the filesystem.
373 // Shall be called from the main thread.
374 void insertSourceImage(const std::string &name, video::IImage *img);
376 // Rebuild images and textures from the current set of source images
377 // Shall be called from the main thread.
378 void rebuildImagesAndTextures();
380 video::ITexture* getNormalTexture(const std::string &name);
381 video::SColor getTextureAverageColor(const std::string &name);
382 video::ITexture *getShaderFlagsTexture(bool normamap_present);
386 // The id of the thread that is allowed to use irrlicht directly
387 std::thread::id m_main_thread;
389 // Cache of source images
390 // This should be only accessed from the main thread
391 SourceImageCache m_sourcecache;
393 // Rebuild images and textures from the current set of source images
394 // Shall be called from the main thread.
395 // You ARE expected to be holding m_textureinfo_cache_mutex
396 void rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti);
398 // Generate a texture
399 u32 generateTexture(const std::string &name);
401 // Generate image based on a string like "stone.png" or "[crack:1:0".
402 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
403 // source_image_names is important to determine when to flush the image from a cache (dynamic media)
404 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg, std::set<std::string> &source_image_names);
406 /*! Generates an image from a full string like
407 * "stone.png^mineral_coal.png^[crack:1:0".
408 * Shall be called from the main thread.
409 * The returned Image should be dropped.
410 * source_image_names is important to determine when to flush the image from a cache (dynamic media)
412 video::IImage* generateImage(const std::string &name, std::set<std::string> &source_image_names);
414 // Thread-safe cache of what source images are known (true = known)
415 MutexedMap<std::string, bool> m_source_image_existence;
417 // A texture id is index in this array.
418 // The first position contains a NULL texture.
419 std::vector<TextureInfo> m_textureinfo_cache;
420 // Maps a texture name to an index in the former.
421 std::map<std::string, u32> m_name_to_id;
422 // The two former containers are behind this mutex
423 std::mutex m_textureinfo_cache_mutex;
425 // Queued texture fetches (to be processed by the main thread)
426 RequestQueue<std::string, u32, std::thread::id, u8> m_get_texture_queue;
428 // Textures that have been overwritten with other ones
429 // but can't be deleted because the ITexture* might still be used
430 std::vector<video::ITexture*> m_texture_trash;
432 // Maps image file names to loaded palettes.
433 std::unordered_map<std::string, Palette> m_palettes;
435 // Cached settings needed for making textures from meshes
436 bool m_setting_mipmap;
437 bool m_setting_trilinear_filter;
438 bool m_setting_bilinear_filter;
441 IWritableTextureSource *createTextureSource()
443 return new TextureSource();
446 TextureSource::TextureSource()
448 m_main_thread = std::this_thread::get_id();
450 // Add a NULL TextureInfo as the first index, named ""
451 m_textureinfo_cache.emplace_back("");
452 m_name_to_id[""] = 0;
454 // Cache some settings
455 // Note: Since this is only done once, the game must be restarted
456 // for these settings to take effect
457 m_setting_mipmap = g_settings->getBool("mip_map");
458 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
459 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
462 TextureSource::~TextureSource()
464 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
466 unsigned int textures_before = driver->getTextureCount();
468 for (const auto &iter : m_textureinfo_cache) {
471 driver->removeTexture(iter.texture);
473 m_textureinfo_cache.clear();
475 for (auto t : m_texture_trash) {
476 //cleanup trashed texture
477 driver->removeTexture(t);
480 infostream << "~TextureSource() before cleanup: "<< textures_before
481 << " after: " << driver->getTextureCount() << std::endl;
484 u32 TextureSource::getTextureId(const std::string &name)
486 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
490 See if texture already exists
492 MutexAutoLock lock(m_textureinfo_cache_mutex);
493 std::map<std::string, u32>::iterator n;
494 n = m_name_to_id.find(name);
495 if (n != m_name_to_id.end())
504 if (std::this_thread::get_id() == m_main_thread) {
505 return generateTexture(name);
509 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
511 // We're gonna ask the result to be put into here
512 static thread_local ResultQueue<std::string, u32, std::thread::id, u8> result_queue;
514 // Throw a request in
515 m_get_texture_queue.add(name, std::this_thread::get_id(), 0, &result_queue);
519 // Wait for result for up to 1 seconds (empirical value)
520 GetResult<std::string, u32, std::thread::id, u8>
521 result = result_queue.pop_front(1000);
523 if (result.key == name) {
527 } catch(ItemNotFoundException &e) {
528 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
532 infostream << "getTextureId(): Failed" << std::endl;
537 // Draw an image on top of another one, using the alpha channel of the
539 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
540 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
542 // Like blit_with_alpha, but only modifies destination pixels that
544 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
545 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
547 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
548 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
549 // color alpha with the destination alpha.
550 // Otherwise, any pixels that are not fully transparent get the color alpha.
551 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
552 const video::SColor &color, int ratio, bool keep_alpha);
554 // paint a texture using the given color
555 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
556 const video::SColor &color);
558 // Apply a mask to an image
559 static void apply_mask(video::IImage *mask, video::IImage *dst,
560 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
562 // Draw or overlay a crack
563 static void draw_crack(video::IImage *crack, video::IImage *dst,
564 bool use_overlay, s32 frame_count, s32 progression,
565 video::IVideoDriver *driver, u8 tiles = 1);
568 void brighten(video::IImage *image);
569 // Parse a transform name
570 u32 parseImageTransform(const std::string& s);
571 // Apply transform to image dimension
572 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
573 // Apply transform to image data
574 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
577 This method generates all the textures
579 u32 TextureSource::generateTexture(const std::string &name)
581 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
583 // Empty name means texture 0
585 infostream<<"generateTexture(): name is empty"<<std::endl;
591 See if texture already exists
593 MutexAutoLock lock(m_textureinfo_cache_mutex);
594 std::map<std::string, u32>::iterator n;
595 n = m_name_to_id.find(name);
596 if (n != m_name_to_id.end()) {
602 Calling only allowed from main thread
604 if (std::this_thread::get_id() != m_main_thread) {
605 errorstream<<"TextureSource::generateTexture() "
606 "called not from main thread"<<std::endl;
610 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
611 sanity_check(driver);
613 // passed into texture info for dynamic media tracking
614 std::set<std::string> source_image_names;
615 video::IImage *img = generateImage(name, source_image_names);
617 video::ITexture *tex = NULL;
621 img = Align2Npot2(img, driver);
623 // Create texture from resulting image
624 tex = driver->addTexture(name.c_str(), img);
625 guiScalingCache(io::path(name.c_str()), driver, img);
630 Add texture to caches (add NULL textures too)
633 MutexAutoLock lock(m_textureinfo_cache_mutex);
635 u32 id = m_textureinfo_cache.size();
636 TextureInfo ti(name, tex, source_image_names);
637 m_textureinfo_cache.push_back(ti);
638 m_name_to_id[name] = id;
643 std::string TextureSource::getTextureName(u32 id)
645 MutexAutoLock lock(m_textureinfo_cache_mutex);
647 if (id >= m_textureinfo_cache.size())
649 errorstream<<"TextureSource::getTextureName(): id="<<id
650 <<" >= m_textureinfo_cache.size()="
651 <<m_textureinfo_cache.size()<<std::endl;
655 return m_textureinfo_cache[id].name;
658 video::ITexture* TextureSource::getTexture(u32 id)
660 MutexAutoLock lock(m_textureinfo_cache_mutex);
662 if (id >= m_textureinfo_cache.size())
665 return m_textureinfo_cache[id].texture;
668 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
670 u32 actual_id = getTextureId(name);
674 return getTexture(actual_id);
677 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
679 static thread_local bool filter_needed =
680 g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
681 ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
682 g_settings->getS32("texture_min_size") > 1);
683 // Avoid duplicating texture if it won't actually change
685 return getTexture(name + "^[applyfiltersformesh", id);
686 return getTexture(name, id);
689 Palette* TextureSource::getPalette(const std::string &name)
691 // Only the main thread may load images
692 sanity_check(std::this_thread::get_id() == m_main_thread);
697 auto it = m_palettes.find(name);
698 if (it == m_palettes.end()) {
700 std::set<std::string> source_image_names; // unused, sadly.
701 video::IImage *img = generateImage(name, source_image_names);
703 warningstream << "TextureSource::getPalette(): palette \"" << name
704 << "\" could not be loaded." << std::endl;
708 u32 w = img->getDimension().Width;
709 u32 h = img->getDimension().Height;
710 // Real area of the image
715 warningstream << "TextureSource::getPalette(): the specified"
716 << " palette image \"" << name << "\" is larger than 256"
717 << " pixels, using the first 256." << std::endl;
719 } else if (256 % area != 0)
720 warningstream << "TextureSource::getPalette(): the "
721 << "specified palette image \"" << name << "\" does not "
722 << "contain power of two pixels." << std::endl;
723 // We stretch the palette so it will fit 256 values
724 // This many param2 values will have the same color
725 u32 step = 256 / area;
726 // For each pixel in the image
727 for (u32 i = 0; i < area; i++) {
728 video::SColor c = img->getPixel(i % w, i / w);
729 // Fill in palette with 'step' colors
730 for (u32 j = 0; j < step; j++)
731 new_palette.push_back(c);
734 // Fill in remaining elements
735 while (new_palette.size() < 256)
736 new_palette.emplace_back(0xFFFFFFFF);
737 m_palettes[name] = new_palette;
738 it = m_palettes.find(name);
740 if (it != m_palettes.end())
741 return &((*it).second);
745 void TextureSource::processQueue()
750 // NOTE: process outstanding requests from all mesh generation threads
751 while (!m_get_texture_queue.empty())
753 GetRequest<std::string, u32, std::thread::id, u8>
754 request = m_get_texture_queue.pop();
756 /*infostream<<"TextureSource::processQueue(): "
757 <<"got texture request with "
758 <<"name=\""<<request.key<<"\""
761 m_get_texture_queue.pushResult(request, generateTexture(request.key));
765 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
767 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
769 sanity_check(std::this_thread::get_id() == m_main_thread);
771 m_sourcecache.insert(name, img, true);
772 m_source_image_existence.set(name, true);
774 // now we need to check for any textures that need updating
775 MutexAutoLock lock(m_textureinfo_cache_mutex);
777 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
778 sanity_check(driver);
780 // Recreate affected textures
782 for (TextureInfo &ti : m_textureinfo_cache) {
784 continue; // Skip dummy entry
785 // If the source image was used, we need to rebuild this texture
786 if (ti.sourceImages.find(name) != ti.sourceImages.end()) {
787 rebuildTexture(driver, ti);
792 verbosestream << "TextureSource: inserting \"" << name << "\" caused rebuild of " << affected << " textures." << std::endl;
795 void TextureSource::rebuildImagesAndTextures()
797 MutexAutoLock lock(m_textureinfo_cache_mutex);
799 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
800 sanity_check(driver);
802 infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
803 << " textures" << std::endl;
806 for (TextureInfo &ti : m_textureinfo_cache) {
808 continue; // Skip dummy entry
809 rebuildTexture(driver, ti);
813 void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
816 return; // this shouldn't happen, just a precaution
818 // replaces the previous sourceImages
819 // shouldn't really need to be done, but can't hurt
820 std::set<std::string> source_image_names;
821 video::IImage *img = generateImage(ti.name, source_image_names);
823 img = Align2Npot2(img, driver);
825 // Create texture from resulting image
826 video::ITexture *t = NULL;
828 t = driver->addTexture(ti.name.c_str(), img);
829 guiScalingCache(io::path(ti.name.c_str()), driver, img);
832 video::ITexture *t_old = ti.texture;
835 ti.sourceImages = source_image_names;
838 m_texture_trash.push_back(t_old);
841 inline static void applyShadeFactor(video::SColor &color, u32 factor)
843 u32 f = core::clamp<u32>(factor, 0, 256);
844 color.setRed(color.getRed() * f / 256);
845 color.setGreen(color.getGreen() * f / 256);
846 color.setBlue(color.getBlue() * f / 256);
849 static video::IImage *createInventoryCubeImage(
850 video::IImage *top, video::IImage *left, video::IImage *right)
852 core::dimension2du size_top = top->getDimension();
853 core::dimension2du size_left = left->getDimension();
854 core::dimension2du size_right = right->getDimension();
856 u32 size = npot2(std::max({
857 size_top.Width, size_top.Height,
858 size_left.Width, size_left.Height,
859 size_right.Width, size_right.Height,
862 // It must be divisible by 4, to let everything work correctly.
863 // But it is a power of 2, so being at least 4 is the same.
864 // And the resulting texture should't be too large as well.
865 size = core::clamp<u32>(size, 4, 64);
867 // With such parameters, the cube fits exactly, touching each image line
868 // from `0` to `cube_size - 1`. (Note that division is exact here).
869 u32 cube_size = 9 * size;
870 u32 offset = size / 2;
872 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
874 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
876 core::dimension2du dim = image->getDimension();
877 video::ECOLOR_FORMAT format = image->getColorFormat();
878 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
879 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
880 image->copyToScaling(scaled);
884 sanity_check(image->getPitch() == 4 * size);
885 return reinterpret_cast<u32 *>(image->getData());
887 auto free_image = [] (video::IImage *image) -> void {
891 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
892 sanity_check(result->getPitch() == 4 * cube_size);
893 result->fill(video::SColor(0x00000000u));
894 u32 *target = reinterpret_cast<u32 *>(result->getData());
896 // Draws single cube face
897 // `shade_factor` is face brightness, in range [0.0, 1.0]
898 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
899 // `offsets` list pixels to be drawn for single source pixel
900 auto draw_image = [=] (video::IImage *image, float shade_factor,
901 s16 xu, s16 xv, s16 x1,
902 s16 yu, s16 yv, s16 y1,
903 std::initializer_list<v2s16> offsets) -> void {
904 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
905 const u32 *source = lock_image(image);
906 for (u16 v = 0; v < size; v++) {
907 for (u16 u = 0; u < size; u++) {
908 video::SColor pixel(*source);
909 applyShadeFactor(pixel, brightness);
910 s16 x = xu * u + xv * v + x1;
911 s16 y = yu * u + yv * v + y1;
912 for (const auto &off : offsets)
913 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
920 draw_image(top, 1.000000f,
921 4, -4, 4 * (size - 1),
924 {2, 0}, {3, 0}, {4, 0}, {5, 0},
925 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
926 {2, 2}, {3, 2}, {4, 2}, {5, 2},
929 draw_image(left, 0.836660f,
934 {0, 1}, {1, 1}, {2, 1}, {3, 1},
935 {0, 2}, {1, 2}, {2, 2}, {3, 2},
936 {0, 3}, {1, 3}, {2, 3}, {3, 3},
937 {0, 4}, {1, 4}, {2, 4}, {3, 4},
941 draw_image(right, 0.670820f,
946 {0, 1}, {1, 1}, {2, 1}, {3, 1},
947 {0, 2}, {1, 2}, {2, 2}, {3, 2},
948 {0, 3}, {1, 3}, {2, 3}, {3, 3},
949 {0, 4}, {1, 4}, {2, 4}, {3, 4},
956 video::IImage* TextureSource::generateImage(const std::string &name, std::set<std::string> &source_image_names)
958 // Get the base image
960 const char separator = '^';
961 const char escape = '\\';
962 const char paren_open = '(';
963 const char paren_close = ')';
965 // Find last separator in the name
966 s32 last_separator_pos = -1;
968 for (s32 i = name.size() - 1; i >= 0; i--) {
969 if (i > 0 && name[i-1] == escape)
973 if (paren_bal == 0) {
974 last_separator_pos = i;
975 i = -1; // break out of loop
979 if (paren_bal == 0) {
980 errorstream << "generateImage(): unbalanced parentheses"
981 << "(extranous '(') while generating texture \""
982 << name << "\"" << std::endl;
995 errorstream << "generateImage(): unbalanced parentheses"
996 << "(missing matching '(') while generating texture \""
997 << name << "\"" << std::endl;
1002 video::IImage *baseimg = NULL;
1005 If separator was found, make the base image
1006 using a recursive call.
1008 if (last_separator_pos != -1) {
1009 baseimg = generateImage(name.substr(0, last_separator_pos), source_image_names);
1013 Parse out the last part of the name of the image and act
1017 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1020 If this name is enclosed in parentheses, generate it
1021 and blit it onto the base image
1023 if (last_part_of_name[0] == paren_open
1024 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1025 std::string name2 = last_part_of_name.substr(1,
1026 last_part_of_name.size() - 2);
1027 video::IImage *tmp = generateImage(name2, source_image_names);
1029 errorstream << "generateImage(): "
1030 "Failed to generate \"" << name2 << "\""
1036 core::dimension2d<u32> dim = tmp->getDimension();
1037 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1042 } else if (!generateImagePart(last_part_of_name, baseimg, source_image_names)) {
1043 // Generate image according to part of name
1044 errorstream << "generateImage(): "
1045 "Failed to generate \"" << last_part_of_name << "\""
1049 // If no resulting image, print a warning
1050 if (baseimg == NULL) {
1051 errorstream << "generateImage(): baseimg is NULL (attempted to"
1052 " create texture \"" << name << "\")" << std::endl;
1061 * Check and align image to npot2 if required by hardware
1062 * @param image image to check for npot2 alignment
1063 * @param driver driver to use for image operations
1064 * @return image or copy of image aligned to npot2
1066 video::IImage *Align2Npot2(video::IImage *image,
1067 video::IVideoDriver *driver)
1072 if (driver->queryFeature(video::EVDF_TEXTURE_NPOT))
1075 core::dimension2d<u32> dim = image->getDimension();
1076 unsigned int height = npot2(dim.Height);
1077 unsigned int width = npot2(dim.Width);
1079 if (dim.Height == height && dim.Width == width)
1082 if (dim.Height > height)
1084 if (dim.Width > width)
1087 video::IImage *targetimage =
1088 driver->createImage(video::ECF_A8R8G8B8,
1089 core::dimension2d<u32>(width, height));
1091 if (targetimage != NULL)
1092 image->copyToScaling(targetimage);
1099 static std::string unescape_string(const std::string &str, const char esc = '\\')
1102 size_t pos = 0, cpos;
1103 out.reserve(str.size());
1105 cpos = str.find_first_of(esc, pos);
1106 if (cpos == std::string::npos) {
1107 out += str.substr(pos);
1110 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1116 void blitBaseImage(video::IImage* &src, video::IImage* &dst)
1118 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1119 // Size of the copied area
1120 core::dimension2d<u32> dim = src->getDimension();
1121 //core::dimension2d<u32> dim(16,16);
1122 // Position to copy the blitted to in the base image
1123 core::position2d<s32> pos_to(0,0);
1124 // Position to copy the blitted from in the blitted image
1125 core::position2d<s32> pos_from(0,0);
1127 /*image->copyToWithAlpha(baseimg, pos_to,
1128 core::rect<s32>(pos_from, dim),
1129 video::SColor(255,255,255,255),
1132 core::dimension2d<u32> dim_dst = dst->getDimension();
1133 if (dim == dim_dst) {
1134 blit_with_alpha(src, dst, pos_from, pos_to, dim);
1135 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1136 // Upscale overlying image
1137 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1138 createImage(video::ECF_A8R8G8B8, dim_dst);
1139 src->copyToScaling(scaled_image);
1141 blit_with_alpha(scaled_image, dst, pos_from, pos_to, dim_dst);
1142 scaled_image->drop();
1144 // Upscale base image
1145 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1146 createImage(video::ECF_A8R8G8B8, dim);
1147 dst->copyToScaling(scaled_base);
1151 blit_with_alpha(src, dst, pos_from, pos_to, dim);
1155 bool TextureSource::generateImagePart(std::string part_of_name,
1156 video::IImage *& baseimg, std::set<std::string> &source_image_names)
1158 const char escape = '\\'; // same as in generateImage()
1159 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1160 sanity_check(driver);
1162 // Stuff starting with [ are special commands
1163 if (part_of_name.empty() || part_of_name[0] != '[') {
1164 source_image_names.insert(part_of_name);
1165 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1166 if (image == NULL) {
1167 if (!part_of_name.empty()) {
1169 // Do not create normalmap dummies
1170 if (part_of_name.find("_normal.png") != std::string::npos) {
1171 warningstream << "generateImage(): Could not load normal map \""
1172 << part_of_name << "\"" << std::endl;
1176 errorstream << "generateImage(): Could not load image \""
1177 << part_of_name << "\" while building texture; "
1178 "Creating a dummy image" << std::endl;
1181 // Just create a dummy image
1182 //core::dimension2d<u32> dim(2,2);
1183 core::dimension2d<u32> dim(1,1);
1184 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1185 sanity_check(image != NULL);
1186 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1187 image->setPixel(1,0, video::SColor(255,0,255,0));
1188 image->setPixel(0,1, video::SColor(255,0,0,255));
1189 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1190 image->setPixel(0,0, video::SColor(255,myrand()%256,
1191 myrand()%256,myrand()%256));
1192 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1193 myrand()%256,myrand()%256));
1194 image->setPixel(0,1, video::SColor(255,myrand()%256,
1195 myrand()%256,myrand()%256));
1196 image->setPixel(1,1, video::SColor(255,myrand()%256,
1197 myrand()%256,myrand()%256));*/
1200 // If base image is NULL, load as base.
1201 if (baseimg == NULL)
1203 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1205 Copy it this way to get an alpha channel.
1206 Otherwise images with alpha cannot be blitted on
1207 images that don't have alpha in the original file.
1209 core::dimension2d<u32> dim = image->getDimension();
1210 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1211 image->copyTo(baseimg);
1213 // Else blit on base.
1216 blitBaseImage(image, baseimg);
1223 // A special texture modification
1225 /*infostream<<"generateImage(): generating special "
1226 <<"modification \""<<part_of_name<<"\""
1232 Adds a cracking texture
1233 N = animation frame count, P = crack progression
1235 if (str_starts_with(part_of_name, "[crack"))
1237 if (baseimg == NULL) {
1238 errorstream<<"generateImagePart(): baseimg == NULL "
1239 <<"for part_of_name=\""<<part_of_name
1240 <<"\", cancelling."<<std::endl;
1244 // Crack image number and overlay option
1245 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1246 bool use_overlay = (part_of_name[6] == 'o');
1247 Strfnd sf(part_of_name);
1249 s32 frame_count = stoi(sf.next(":"));
1250 s32 progression = stoi(sf.next(":"));
1252 // Check whether there is the <tiles> argument, that is,
1253 // whether there are 3 arguments. If so, shift values
1254 // as the first and not the last argument is optional.
1255 auto s = sf.next(":");
1257 tiles = frame_count;
1258 frame_count = progression;
1259 progression = stoi(s);
1262 if (progression >= 0) {
1266 It is an image with a number of cracking stages
1269 video::IImage *img_crack = m_sourcecache.getOrLoad(
1270 "crack_anylength.png");
1273 draw_crack(img_crack, baseimg,
1274 use_overlay, frame_count,
1275 progression, driver, tiles);
1281 [combine:WxH:X,Y=filename:X,Y=filename2
1282 Creates a bigger texture from any amount of smaller ones
1284 else if (str_starts_with(part_of_name, "[combine"))
1286 Strfnd sf(part_of_name);
1288 u32 w0 = stoi(sf.next("x"));
1289 u32 h0 = stoi(sf.next(":"));
1290 core::dimension2d<u32> dim(w0,h0);
1291 if (baseimg == NULL) {
1292 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1293 baseimg->fill(video::SColor(0,0,0,0));
1295 while (!sf.at_end()) {
1296 u32 x = stoi(sf.next(","));
1297 u32 y = stoi(sf.next("="));
1298 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1299 infostream<<"Adding \""<<filename
1300 <<"\" to combined ("<<x<<","<<y<<")"
1302 video::IImage *img = generateImage(filename, source_image_names);
1304 core::dimension2d<u32> dim = img->getDimension();
1305 core::position2d<s32> pos_base(x, y);
1306 video::IImage *img2 =
1307 driver->createImage(video::ECF_A8R8G8B8, dim);
1310 /*img2->copyToWithAlpha(baseimg, pos_base,
1311 core::rect<s32>(v2s32(0,0), dim),
1312 video::SColor(255,255,255,255),
1314 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1317 errorstream << "generateImagePart(): Failed to load image \""
1318 << filename << "\" for [combine" << std::endl;
1325 else if (str_starts_with(part_of_name, "[brighten"))
1327 if (baseimg == NULL) {
1328 errorstream<<"generateImagePart(): baseimg==NULL "
1329 <<"for part_of_name=\""<<part_of_name
1330 <<"\", cancelling."<<std::endl;
1338 Make image completely opaque.
1339 Used for the leaves texture when in old leaves mode, so
1340 that the transparent parts don't look completely black
1341 when simple alpha channel is used for rendering.
1343 else if (str_starts_with(part_of_name, "[noalpha"))
1345 if (baseimg == NULL){
1346 errorstream<<"generateImagePart(): baseimg==NULL "
1347 <<"for part_of_name=\""<<part_of_name
1348 <<"\", cancelling."<<std::endl;
1352 core::dimension2d<u32> dim = baseimg->getDimension();
1354 // Set alpha to full
1355 for (u32 y=0; y<dim.Height; y++)
1356 for (u32 x=0; x<dim.Width; x++)
1358 video::SColor c = baseimg->getPixel(x,y);
1360 baseimg->setPixel(x,y,c);
1365 Convert one color to transparent.
1367 else if (str_starts_with(part_of_name, "[makealpha:"))
1369 if (baseimg == NULL) {
1370 errorstream<<"generateImagePart(): baseimg == NULL "
1371 <<"for part_of_name=\""<<part_of_name
1372 <<"\", cancelling."<<std::endl;
1376 Strfnd sf(part_of_name.substr(11));
1377 u32 r1 = stoi(sf.next(","));
1378 u32 g1 = stoi(sf.next(","));
1379 u32 b1 = stoi(sf.next(""));
1381 core::dimension2d<u32> dim = baseimg->getDimension();
1383 /*video::IImage *oldbaseimg = baseimg;
1384 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1385 oldbaseimg->copyTo(baseimg);
1386 oldbaseimg->drop();*/
1388 // Set alpha to full
1389 for (u32 y=0; y<dim.Height; y++)
1390 for (u32 x=0; x<dim.Width; x++)
1392 video::SColor c = baseimg->getPixel(x,y);
1394 u32 g = c.getGreen();
1395 u32 b = c.getBlue();
1396 if (!(r == r1 && g == g1 && b == b1))
1399 baseimg->setPixel(x,y,c);
1404 Rotates and/or flips the image.
1406 N can be a number (between 0 and 7) or a transform name.
1407 Rotations are counter-clockwise.
1409 1 R90 rotate by 90 degrees
1410 2 R180 rotate by 180 degrees
1411 3 R270 rotate by 270 degrees
1413 5 FXR90 flip X then rotate by 90 degrees
1415 7 FYR90 flip Y then rotate by 90 degrees
1417 Note: Transform names can be concatenated to produce
1418 their product (applies the first then the second).
1419 The resulting transform will be equivalent to one of the
1420 eight existing ones, though (see: dihedral group).
1422 else if (str_starts_with(part_of_name, "[transform"))
1424 if (baseimg == NULL) {
1425 errorstream<<"generateImagePart(): baseimg == NULL "
1426 <<"for part_of_name=\""<<part_of_name
1427 <<"\", cancelling."<<std::endl;
1431 u32 transform = parseImageTransform(part_of_name.substr(10));
1432 core::dimension2d<u32> dim = imageTransformDimension(
1433 transform, baseimg->getDimension());
1434 video::IImage *image = driver->createImage(
1435 baseimg->getColorFormat(), dim);
1436 sanity_check(image != NULL);
1437 imageTransform(transform, baseimg, image);
1442 [inventorycube{topimage{leftimage{rightimage
1443 In every subimage, replace ^ with &.
1444 Create an "inventory cube".
1445 NOTE: This should be used only on its own.
1446 Example (a grass block (not actually used in game):
1447 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1449 else if (str_starts_with(part_of_name, "[inventorycube"))
1451 if (baseimg != NULL){
1452 errorstream<<"generateImagePart(): baseimg != NULL "
1453 <<"for part_of_name=\""<<part_of_name
1454 <<"\", cancelling."<<std::endl;
1458 str_replace(part_of_name, '&', '^');
1459 Strfnd sf(part_of_name);
1461 std::string imagename_top = sf.next("{");
1462 std::string imagename_left = sf.next("{");
1463 std::string imagename_right = sf.next("{");
1465 // Generate images for the faces of the cube
1466 video::IImage *img_top = generateImage(imagename_top, source_image_names);
1467 video::IImage *img_left = generateImage(imagename_left, source_image_names);
1468 video::IImage *img_right = generateImage(imagename_right, source_image_names);
1470 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1471 errorstream << "generateImagePart(): Failed to create textures"
1472 << " for inventorycube \"" << part_of_name << "\""
1474 baseimg = generateImage(imagename_top, source_image_names);
1478 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1480 // Face images are not needed anymore
1488 [lowpart:percent:filename
1489 Adds the lower part of a texture
1491 else if (str_starts_with(part_of_name, "[lowpart:"))
1493 Strfnd sf(part_of_name);
1495 u32 percent = stoi(sf.next(":"));
1496 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1498 if (baseimg == NULL)
1499 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1500 video::IImage *img = generateImage(filename, source_image_names);
1503 core::dimension2d<u32> dim = img->getDimension();
1504 core::position2d<s32> pos_base(0, 0);
1505 video::IImage *img2 =
1506 driver->createImage(video::ECF_A8R8G8B8, dim);
1509 core::position2d<s32> clippos(0, 0);
1510 clippos.Y = dim.Height * (100-percent) / 100;
1511 core::dimension2d<u32> clipdim = dim;
1512 clipdim.Height = clipdim.Height * percent / 100 + 1;
1513 core::rect<s32> cliprect(clippos, clipdim);
1514 img2->copyToWithAlpha(baseimg, pos_base,
1515 core::rect<s32>(v2s32(0,0), dim),
1516 video::SColor(255,255,255,255),
1523 Crops a frame of a vertical animation.
1524 N = frame count, I = frame index
1526 else if (str_starts_with(part_of_name, "[verticalframe:"))
1528 Strfnd sf(part_of_name);
1530 u32 frame_count = stoi(sf.next(":"));
1531 u32 frame_index = stoi(sf.next(":"));
1533 if (baseimg == NULL){
1534 errorstream<<"generateImagePart(): baseimg != NULL "
1535 <<"for part_of_name=\""<<part_of_name
1536 <<"\", cancelling."<<std::endl;
1540 v2u32 frame_size = baseimg->getDimension();
1541 frame_size.Y /= frame_count;
1543 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1546 errorstream<<"generateImagePart(): Could not create image "
1547 <<"for part_of_name=\""<<part_of_name
1548 <<"\", cancelling."<<std::endl;
1552 // Fill target image with transparency
1553 img->fill(video::SColor(0,0,0,0));
1555 core::dimension2d<u32> dim = frame_size;
1556 core::position2d<s32> pos_dst(0, 0);
1557 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1558 baseimg->copyToWithAlpha(img, pos_dst,
1559 core::rect<s32>(pos_src, dim),
1560 video::SColor(255,255,255,255),
1568 Applies a mask to an image
1570 else if (str_starts_with(part_of_name, "[mask:"))
1572 if (baseimg == NULL) {
1573 errorstream << "generateImage(): baseimg == NULL "
1574 << "for part_of_name=\"" << part_of_name
1575 << "\", cancelling." << std::endl;
1578 Strfnd sf(part_of_name);
1580 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1582 video::IImage *img = generateImage(filename, source_image_names);
1584 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1585 img->getDimension());
1588 errorstream << "generateImage(): Failed to load \""
1589 << filename << "\".";
1594 multiplys a given color to any pixel of an image
1595 color = color as ColorString
1597 else if (str_starts_with(part_of_name, "[multiply:")) {
1598 Strfnd sf(part_of_name);
1600 std::string color_str = sf.next(":");
1602 if (baseimg == NULL) {
1603 errorstream << "generateImagePart(): baseimg != NULL "
1604 << "for part_of_name=\"" << part_of_name
1605 << "\", cancelling." << std::endl;
1609 video::SColor color;
1611 if (!parseColorString(color_str, color, false))
1614 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1618 Overlays image with given color
1619 color = color as ColorString
1621 else if (str_starts_with(part_of_name, "[colorize:"))
1623 Strfnd sf(part_of_name);
1625 std::string color_str = sf.next(":");
1626 std::string ratio_str = sf.next(":");
1628 if (baseimg == NULL) {
1629 errorstream << "generateImagePart(): baseimg != NULL "
1630 << "for part_of_name=\"" << part_of_name
1631 << "\", cancelling." << std::endl;
1635 video::SColor color;
1637 bool keep_alpha = false;
1639 if (!parseColorString(color_str, color, false))
1642 if (is_number(ratio_str))
1643 ratio = mystoi(ratio_str, 0, 255);
1644 else if (ratio_str == "alpha")
1647 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1650 [applyfiltersformesh
1653 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1655 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1659 errorstream << "generateImagePart(): baseimg == NULL "
1660 << "for part_of_name=\"" << part_of_name
1661 << "\", cancelling." << std::endl;
1665 // Apply the "clean transparent" filter, if needed
1666 if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
1667 imageCleanTransparent(baseimg, 127);
1669 /* Upscale textures to user's requested minimum size. This is a trick to make
1670 * filters look as good on low-res textures as on high-res ones, by making
1671 * low-res textures BECOME high-res ones. This is helpful for worlds that
1672 * mix high- and low-res textures, or for mods with least-common-denominator
1673 * textures that don't have the resources to offer high-res alternatives.
1675 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1676 const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1;
1678 const core::dimension2d<u32> dim = baseimg->getDimension();
1680 /* Calculate scaling needed to make the shortest texture dimension
1681 * equal to the target minimum. If e.g. this is a vertical frames
1682 * animation, the short dimension will be the real size.
1684 if ((dim.Width == 0) || (dim.Height == 0)) {
1685 errorstream << "generateImagePart(): Illegal 0 dimension "
1686 << "for part_of_name=\""<< part_of_name
1687 << "\", cancelling." << std::endl;
1690 u32 xscale = scaleto / dim.Width;
1691 u32 yscale = scaleto / dim.Height;
1692 u32 scale = (xscale > yscale) ? xscale : yscale;
1694 // Never downscale; only scale up by 2x or more.
1696 u32 w = scale * dim.Width;
1697 u32 h = scale * dim.Height;
1698 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1699 video::IImage *newimg = driver->createImage(
1700 baseimg->getColorFormat(), newdim);
1701 baseimg->copyToScaling(newimg);
1709 Resizes the base image to the given dimensions
1711 else if (str_starts_with(part_of_name, "[resize"))
1713 if (baseimg == NULL) {
1714 errorstream << "generateImagePart(): baseimg == NULL "
1715 << "for part_of_name=\""<< part_of_name
1716 << "\", cancelling." << std::endl;
1720 Strfnd sf(part_of_name);
1722 u32 width = stoi(sf.next("x"));
1723 u32 height = stoi(sf.next(""));
1724 core::dimension2d<u32> dim(width, height);
1726 video::IImage *image = RenderingEngine::get_video_driver()->
1727 createImage(video::ECF_A8R8G8B8, dim);
1728 baseimg->copyToScaling(image);
1734 Makes the base image transparent according to the given ratio.
1735 R must be between 0 and 255.
1736 0 means totally transparent.
1737 255 means totally opaque.
1739 else if (str_starts_with(part_of_name, "[opacity:")) {
1740 if (baseimg == NULL) {
1741 errorstream << "generateImagePart(): baseimg == NULL "
1742 << "for part_of_name=\"" << part_of_name
1743 << "\", cancelling." << std::endl;
1747 Strfnd sf(part_of_name);
1750 u32 ratio = mystoi(sf.next(""), 0, 255);
1752 core::dimension2d<u32> dim = baseimg->getDimension();
1754 for (u32 y = 0; y < dim.Height; y++)
1755 for (u32 x = 0; x < dim.Width; x++)
1757 video::SColor c = baseimg->getPixel(x, y);
1758 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1759 baseimg->setPixel(x, y, c);
1764 Inverts the given channels of the base image.
1765 Mode may contain the characters "r", "g", "b", "a".
1766 Only the channels that are mentioned in the mode string
1769 else if (str_starts_with(part_of_name, "[invert:")) {
1770 if (baseimg == NULL) {
1771 errorstream << "generateImagePart(): baseimg == NULL "
1772 << "for part_of_name=\"" << part_of_name
1773 << "\", cancelling." << std::endl;
1777 Strfnd sf(part_of_name);
1780 std::string mode = sf.next("");
1782 if (mode.find('a') != std::string::npos)
1783 mask |= 0xff000000UL;
1784 if (mode.find('r') != std::string::npos)
1785 mask |= 0x00ff0000UL;
1786 if (mode.find('g') != std::string::npos)
1787 mask |= 0x0000ff00UL;
1788 if (mode.find('b') != std::string::npos)
1789 mask |= 0x000000ffUL;
1791 core::dimension2d<u32> dim = baseimg->getDimension();
1793 for (u32 y = 0; y < dim.Height; y++)
1794 for (u32 x = 0; x < dim.Width; x++)
1796 video::SColor c = baseimg->getPixel(x, y);
1798 baseimg->setPixel(x, y, c);
1803 Retrieves a tile at position X,Y (in tiles)
1804 from the base image it assumes to be a
1805 tilesheet with dimensions W,H (in tiles).
1807 else if (part_of_name.substr(0,7) == "[sheet:") {
1808 if (baseimg == NULL) {
1809 errorstream << "generateImagePart(): baseimg != NULL "
1810 << "for part_of_name=\"" << part_of_name
1811 << "\", cancelling." << std::endl;
1815 Strfnd sf(part_of_name);
1817 u32 w0 = stoi(sf.next("x"));
1818 u32 h0 = stoi(sf.next(":"));
1819 u32 x0 = stoi(sf.next(","));
1820 u32 y0 = stoi(sf.next(":"));
1822 core::dimension2d<u32> img_dim = baseimg->getDimension();
1823 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1825 video::IImage *img = driver->createImage(
1826 video::ECF_A8R8G8B8, tile_dim);
1828 errorstream << "generateImagePart(): Could not create image "
1829 << "for part_of_name=\"" << part_of_name
1830 << "\", cancelling." << std::endl;
1834 img->fill(video::SColor(0,0,0,0));
1835 v2u32 vdim(tile_dim);
1836 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1837 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1838 video::SColor(255,255,255,255), NULL);
1846 Decodes a PNG image in base64 form.
1847 Use minetest.encode_png and minetest.encode_base64
1848 to produce a valid string.
1850 else if (str_starts_with(part_of_name, "[png:")) {
1851 Strfnd sf(part_of_name);
1855 std::string blob = sf.next("");
1856 if (!base64_is_valid(blob)) {
1857 errorstream << "generateImagePart(): "
1858 << "malformed base64 in '[png'"
1862 png = base64_decode(blob);
1865 auto *device = RenderingEngine::get_raw_device();
1866 auto *fs = device->getFileSystem();
1867 auto *vd = device->getVideoDriver();
1868 auto *memfile = fs->createMemoryReadFile(png.data(), png.size(), "__temp_png");
1869 video::IImage* pngimg = vd->createImageFromFile(memfile);
1873 blitBaseImage(pngimg, baseimg);
1875 core::dimension2d<u32> dim = pngimg->getDimension();
1876 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1877 pngimg->copyTo(baseimg);
1883 errorstream << "generateImagePart(): Invalid "
1884 " modification: \"" << part_of_name << "\"" << std::endl;
1892 Calculate the color of a single pixel drawn on top of another pixel.
1894 This is a little more complicated than just video::SColor::getInterpolated
1895 because getInterpolated does not handle alpha correctly. For example, a
1896 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1897 pixel with alpha=160, while getInterpolated would yield alpha=96.
1899 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1901 if (dst_c.getAlpha() == 0)
1903 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1904 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1905 src_c.getAlpha() * ratio / (255 * 255));
1910 Draw an image on top of another one, using the alpha channel of the
1913 This exists because IImage::copyToWithAlpha() doesn't seem to always
1916 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1917 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1919 for (u32 y0=0; y0<size.Y; y0++)
1920 for (u32 x0=0; x0<size.X; x0++)
1922 s32 src_x = src_pos.X + x0;
1923 s32 src_y = src_pos.Y + y0;
1924 s32 dst_x = dst_pos.X + x0;
1925 s32 dst_y = dst_pos.Y + y0;
1926 video::SColor src_c = src->getPixel(src_x, src_y);
1927 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1928 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1929 dst->setPixel(dst_x, dst_y, dst_c);
1934 Draw an image on top of another one, using the alpha channel of the
1935 source image; only modify fully opaque pixels in destinaion
1937 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1938 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1940 for (u32 y0=0; y0<size.Y; y0++)
1941 for (u32 x0=0; x0<size.X; x0++)
1943 s32 src_x = src_pos.X + x0;
1944 s32 src_y = src_pos.Y + y0;
1945 s32 dst_x = dst_pos.X + x0;
1946 s32 dst_y = dst_pos.Y + y0;
1947 video::SColor src_c = src->getPixel(src_x, src_y);
1948 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1949 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1951 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1952 dst->setPixel(dst_x, dst_y, dst_c);
1957 // This function has been disabled because it is currently unused.
1958 // Feel free to re-enable if you find it handy.
1961 Draw an image on top of another one, using the specified ratio
1962 modify all partially-opaque pixels in the destination.
1964 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1965 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1967 for (u32 y0 = 0; y0 < size.Y; y0++)
1968 for (u32 x0 = 0; x0 < size.X; x0++)
1970 s32 src_x = src_pos.X + x0;
1971 s32 src_y = src_pos.Y + y0;
1972 s32 dst_x = dst_pos.X + x0;
1973 s32 dst_y = dst_pos.Y + y0;
1974 video::SColor src_c = src->getPixel(src_x, src_y);
1975 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1976 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1979 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1981 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1982 dst->setPixel(dst_x, dst_y, dst_c);
1989 Apply color to destination
1991 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1992 const video::SColor &color, int ratio, bool keep_alpha)
1994 u32 alpha = color.getAlpha();
1995 video::SColor dst_c;
1996 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1997 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1999 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2000 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2001 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2002 if (dst_alpha > 0) {
2003 dst_c.setAlpha(dst_alpha * alpha / 255);
2004 dst->setPixel(x, y, dst_c);
2007 } else { // replace the color including the alpha
2008 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2009 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2010 if (dst->getPixel(x, y).getAlpha() > 0)
2011 dst->setPixel(x, y, color);
2013 } else { // interpolate between the color and destination
2014 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2015 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2016 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2017 dst_c = dst->getPixel(x, y);
2018 if (dst_c.getAlpha() > 0) {
2019 dst_c = color.getInterpolated(dst_c, interp);
2020 dst->setPixel(x, y, dst_c);
2027 Apply color to destination
2029 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2030 const video::SColor &color)
2032 video::SColor dst_c;
2034 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2035 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2036 dst_c = dst->getPixel(x, y);
2039 (dst_c.getRed() * color.getRed()) / 255,
2040 (dst_c.getGreen() * color.getGreen()) / 255,
2041 (dst_c.getBlue() * color.getBlue()) / 255
2043 dst->setPixel(x, y, dst_c);
2048 Apply mask to destination
2050 static void apply_mask(video::IImage *mask, video::IImage *dst,
2051 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2053 for (u32 y0 = 0; y0 < size.Y; y0++) {
2054 for (u32 x0 = 0; x0 < size.X; x0++) {
2055 s32 mask_x = x0 + mask_pos.X;
2056 s32 mask_y = y0 + mask_pos.Y;
2057 s32 dst_x = x0 + dst_pos.X;
2058 s32 dst_y = y0 + dst_pos.Y;
2059 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2060 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2061 dst_c.color &= mask_c.color;
2062 dst->setPixel(dst_x, dst_y, dst_c);
2067 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2068 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2070 core::dimension2d<u32> strip_size = crack->getDimension();
2071 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2072 core::dimension2d<u32> tile_size(size / tiles);
2073 s32 frame_count = strip_size.Height / strip_size.Width;
2074 if (frame_index >= frame_count)
2075 frame_index = frame_count - 1;
2076 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2077 video::IImage *result = nullptr;
2079 // extract crack frame
2080 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2083 if (tile_size == frame_size) {
2084 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2086 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2088 goto exit__has_tile;
2089 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2090 crack_frame->copyToScaling(crack_tile);
2091 crack_frame->drop();
2097 result = driver->createImage(video::ECF_A8R8G8B8, size);
2099 goto exit__has_tile;
2101 for (u8 i = 0; i < tiles; i++)
2102 for (u8 j = 0; j < tiles; j++)
2103 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2110 static void draw_crack(video::IImage *crack, video::IImage *dst,
2111 bool use_overlay, s32 frame_count, s32 progression,
2112 video::IVideoDriver *driver, u8 tiles)
2114 // Dimension of destination image
2115 core::dimension2d<u32> dim_dst = dst->getDimension();
2116 // Limit frame_count
2117 if (frame_count > (s32) dim_dst.Height)
2118 frame_count = dim_dst.Height;
2119 if (frame_count < 1)
2121 // Dimension of the scaled crack stage,
2122 // which is the same as the dimension of a single destination frame
2123 core::dimension2d<u32> frame_size(
2125 dim_dst.Height / frame_count
2127 video::IImage *crack_scaled = create_crack_image(crack, progression,
2128 frame_size, tiles, driver);
2132 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2133 for (s32 i = 0; i < frame_count; ++i) {
2134 v2s32 dst_pos(0, frame_size.Height * i);
2135 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2138 crack_scaled->drop();
2141 void brighten(video::IImage *image)
2146 core::dimension2d<u32> dim = image->getDimension();
2148 for (u32 y=0; y<dim.Height; y++)
2149 for (u32 x=0; x<dim.Width; x++)
2151 video::SColor c = image->getPixel(x,y);
2152 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2153 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2154 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2155 image->setPixel(x,y,c);
2159 u32 parseImageTransform(const std::string& s)
2161 int total_transform = 0;
2163 std::string transform_names[8];
2164 transform_names[0] = "i";
2165 transform_names[1] = "r90";
2166 transform_names[2] = "r180";
2167 transform_names[3] = "r270";
2168 transform_names[4] = "fx";
2169 transform_names[6] = "fy";
2171 std::size_t pos = 0;
2172 while(pos < s.size())
2175 for (int i = 0; i <= 7; ++i)
2177 const std::string &name_i = transform_names[i];
2179 if (s[pos] == ('0' + i))
2186 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2188 pos += name_i.size();
2195 // Multiply total_transform and transform in the group D4
2198 new_total = (transform + total_transform) % 4;
2200 new_total = (transform - total_transform + 8) % 4;
2201 if ((transform >= 4) ^ (total_transform >= 4))
2204 total_transform = new_total;
2206 return total_transform;
2209 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2211 if (transform % 2 == 0)
2214 return core::dimension2d<u32>(dim.Height, dim.Width);
2217 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2219 if (src == NULL || dst == NULL)
2222 core::dimension2d<u32> dstdim = dst->getDimension();
2225 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2226 assert(transform <= 7);
2229 Compute the transformation from source coordinates (sx,sy)
2230 to destination coordinates (dx,dy).
2234 if (transform == 0) // identity
2235 sxn = 0, syn = 2; // sx = dx, sy = dy
2236 else if (transform == 1) // rotate by 90 degrees ccw
2237 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2238 else if (transform == 2) // rotate by 180 degrees
2239 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2240 else if (transform == 3) // rotate by 270 degrees ccw
2241 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2242 else if (transform == 4) // flip x
2243 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2244 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2245 sxn = 2, syn = 0; // sx = dy, sy = dx
2246 else if (transform == 6) // flip y
2247 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2248 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2249 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2251 for (u32 dy=0; dy<dstdim.Height; dy++)
2252 for (u32 dx=0; dx<dstdim.Width; dx++)
2254 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2255 u32 sx = entries[sxn];
2256 u32 sy = entries[syn];
2257 video::SColor c = src->getPixel(sx,sy);
2258 dst->setPixel(dx,dy,c);
2262 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2264 if (isKnownSourceImage("override_normal.png"))
2265 return getTexture("override_normal.png");
2266 std::string fname_base = name;
2267 static const char *normal_ext = "_normal.png";
2268 static const u32 normal_ext_size = strlen(normal_ext);
2269 size_t pos = fname_base.find('.');
2270 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2271 if (isKnownSourceImage(fname_normal)) {
2272 // look for image extension and replace it
2274 while ((i = fname_base.find('.', i)) != std::string::npos) {
2275 fname_base.replace(i, 4, normal_ext);
2276 i += normal_ext_size;
2278 return getTexture(fname_base);
2284 // For more colourspace transformations, see for example
2285 // https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
2287 inline float linear_to_srgb_component(float v)
2290 return 1.055f * powf(v, 1.0f / 2.4f) - 0.055f;
2293 inline float srgb_to_linear_component(float v)
2296 return powf((v + 0.055f) / 1.055f, 2.4f);
2300 v3f srgb_to_linear(const video::SColor &col_srgb)
2302 v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue());
2304 col.X = srgb_to_linear_component(col.X);
2305 col.Y = srgb_to_linear_component(col.Y);
2306 col.Z = srgb_to_linear_component(col.Z);
2310 video::SColor linear_to_srgb(const v3f &col_linear)
2313 col.X = linear_to_srgb_component(col_linear.X);
2314 col.Y = linear_to_srgb_component(col_linear.Y);
2315 col.Z = linear_to_srgb_component(col_linear.Z);
2317 col.X = core::clamp<float>(col.X, 0.0f, 255.0f);
2318 col.Y = core::clamp<float>(col.Y, 0.0f, 255.0f);
2319 col.Z = core::clamp<float>(col.Z, 0.0f, 255.0f);
2320 return video::SColor(0xff, myround(col.X), myround(col.Y),
2325 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2327 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2328 video::SColor c(0, 0, 0, 0);
2329 video::ITexture *texture = getTexture(name);
2332 video::IImage *image = driver->createImage(texture,
2333 core::position2d<s32>(0, 0),
2334 texture->getOriginalSize());
2339 v3f col_acc(0, 0, 0);
2340 core::dimension2d<u32> dim = image->getDimension();
2343 step = dim.Width / 16;
2344 for (u16 x = 0; x < dim.Width; x += step) {
2345 for (u16 y = 0; y < dim.Width; y += step) {
2346 c = image->getPixel(x,y);
2347 if (c.getAlpha() > 0) {
2349 col_acc += srgb_to_linear(c);
2356 c = linear_to_srgb(col_acc);
2363 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2365 std::string tname = "__shaderFlagsTexture";
2366 tname += normalmap_present ? "1" : "0";
2368 if (isKnownSourceImage(tname)) {
2369 return getTexture(tname);
2372 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2373 video::IImage *flags_image = driver->createImage(
2374 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2375 sanity_check(flags_image != NULL);
2376 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2377 flags_image->setPixel(0, 0, c);
2378 insertSourceImage(tname, flags_image);
2379 flags_image->drop();
2380 return getTexture(tname);
2384 std::vector<std::string> getTextureDirs()
2386 return fs::GetRecursiveDirs(g_settings->get("texture_path"));