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 errorstream << "generateImagePart(): Invalid PNG data" << std::endl;
1878 blitBaseImage(pngimg, baseimg);
1880 core::dimension2d<u32> dim = pngimg->getDimension();
1881 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1882 pngimg->copyTo(baseimg);
1888 errorstream << "generateImagePart(): Invalid "
1889 " modification: \"" << part_of_name << "\"" << std::endl;
1897 Calculate the color of a single pixel drawn on top of another pixel.
1899 This is a little more complicated than just video::SColor::getInterpolated
1900 because getInterpolated does not handle alpha correctly. For example, a
1901 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1902 pixel with alpha=160, while getInterpolated would yield alpha=96.
1904 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1906 if (dst_c.getAlpha() == 0)
1908 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1909 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1910 src_c.getAlpha() * ratio / (255 * 255));
1915 Draw an image on top of another one, using the alpha channel of the
1918 This exists because IImage::copyToWithAlpha() doesn't seem to always
1921 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1922 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1924 for (u32 y0=0; y0<size.Y; y0++)
1925 for (u32 x0=0; x0<size.X; x0++)
1927 s32 src_x = src_pos.X + x0;
1928 s32 src_y = src_pos.Y + y0;
1929 s32 dst_x = dst_pos.X + x0;
1930 s32 dst_y = dst_pos.Y + y0;
1931 video::SColor src_c = src->getPixel(src_x, src_y);
1932 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1933 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1934 dst->setPixel(dst_x, dst_y, dst_c);
1939 Draw an image on top of another one, using the alpha channel of the
1940 source image; only modify fully opaque pixels in destinaion
1942 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1943 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1945 for (u32 y0=0; y0<size.Y; y0++)
1946 for (u32 x0=0; x0<size.X; x0++)
1948 s32 src_x = src_pos.X + x0;
1949 s32 src_y = src_pos.Y + y0;
1950 s32 dst_x = dst_pos.X + x0;
1951 s32 dst_y = dst_pos.Y + y0;
1952 video::SColor src_c = src->getPixel(src_x, src_y);
1953 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1954 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1956 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1957 dst->setPixel(dst_x, dst_y, dst_c);
1962 // This function has been disabled because it is currently unused.
1963 // Feel free to re-enable if you find it handy.
1966 Draw an image on top of another one, using the specified ratio
1967 modify all partially-opaque pixels in the destination.
1969 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1970 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1972 for (u32 y0 = 0; y0 < size.Y; y0++)
1973 for (u32 x0 = 0; x0 < size.X; x0++)
1975 s32 src_x = src_pos.X + x0;
1976 s32 src_y = src_pos.Y + y0;
1977 s32 dst_x = dst_pos.X + x0;
1978 s32 dst_y = dst_pos.Y + y0;
1979 video::SColor src_c = src->getPixel(src_x, src_y);
1980 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1981 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1984 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1986 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1987 dst->setPixel(dst_x, dst_y, dst_c);
1994 Apply color to destination
1996 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1997 const video::SColor &color, int ratio, bool keep_alpha)
1999 u32 alpha = color.getAlpha();
2000 video::SColor dst_c;
2001 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2002 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2004 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2005 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2006 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2007 if (dst_alpha > 0) {
2008 dst_c.setAlpha(dst_alpha * alpha / 255);
2009 dst->setPixel(x, y, dst_c);
2012 } else { // replace the color including the alpha
2013 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2014 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2015 if (dst->getPixel(x, y).getAlpha() > 0)
2016 dst->setPixel(x, y, color);
2018 } else { // interpolate between the color and destination
2019 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2020 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2021 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2022 dst_c = dst->getPixel(x, y);
2023 if (dst_c.getAlpha() > 0) {
2024 dst_c = color.getInterpolated(dst_c, interp);
2025 dst->setPixel(x, y, dst_c);
2032 Apply color to destination
2034 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2035 const video::SColor &color)
2037 video::SColor dst_c;
2039 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2040 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2041 dst_c = dst->getPixel(x, y);
2044 (dst_c.getRed() * color.getRed()) / 255,
2045 (dst_c.getGreen() * color.getGreen()) / 255,
2046 (dst_c.getBlue() * color.getBlue()) / 255
2048 dst->setPixel(x, y, dst_c);
2053 Apply mask to destination
2055 static void apply_mask(video::IImage *mask, video::IImage *dst,
2056 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2058 for (u32 y0 = 0; y0 < size.Y; y0++) {
2059 for (u32 x0 = 0; x0 < size.X; x0++) {
2060 s32 mask_x = x0 + mask_pos.X;
2061 s32 mask_y = y0 + mask_pos.Y;
2062 s32 dst_x = x0 + dst_pos.X;
2063 s32 dst_y = y0 + dst_pos.Y;
2064 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2065 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2066 dst_c.color &= mask_c.color;
2067 dst->setPixel(dst_x, dst_y, dst_c);
2072 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2073 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2075 core::dimension2d<u32> strip_size = crack->getDimension();
2076 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2077 core::dimension2d<u32> tile_size(size / tiles);
2078 s32 frame_count = strip_size.Height / strip_size.Width;
2079 if (frame_index >= frame_count)
2080 frame_index = frame_count - 1;
2081 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2082 video::IImage *result = nullptr;
2084 // extract crack frame
2085 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2088 if (tile_size == frame_size) {
2089 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2091 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2093 goto exit__has_tile;
2094 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2095 crack_frame->copyToScaling(crack_tile);
2096 crack_frame->drop();
2102 result = driver->createImage(video::ECF_A8R8G8B8, size);
2104 goto exit__has_tile;
2106 for (u8 i = 0; i < tiles; i++)
2107 for (u8 j = 0; j < tiles; j++)
2108 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2115 static void draw_crack(video::IImage *crack, video::IImage *dst,
2116 bool use_overlay, s32 frame_count, s32 progression,
2117 video::IVideoDriver *driver, u8 tiles)
2119 // Dimension of destination image
2120 core::dimension2d<u32> dim_dst = dst->getDimension();
2121 // Limit frame_count
2122 if (frame_count > (s32) dim_dst.Height)
2123 frame_count = dim_dst.Height;
2124 if (frame_count < 1)
2126 // Dimension of the scaled crack stage,
2127 // which is the same as the dimension of a single destination frame
2128 core::dimension2d<u32> frame_size(
2130 dim_dst.Height / frame_count
2132 video::IImage *crack_scaled = create_crack_image(crack, progression,
2133 frame_size, tiles, driver);
2137 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2138 for (s32 i = 0; i < frame_count; ++i) {
2139 v2s32 dst_pos(0, frame_size.Height * i);
2140 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2143 crack_scaled->drop();
2146 void brighten(video::IImage *image)
2151 core::dimension2d<u32> dim = image->getDimension();
2153 for (u32 y=0; y<dim.Height; y++)
2154 for (u32 x=0; x<dim.Width; x++)
2156 video::SColor c = image->getPixel(x,y);
2157 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2158 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2159 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2160 image->setPixel(x,y,c);
2164 u32 parseImageTransform(const std::string& s)
2166 int total_transform = 0;
2168 std::string transform_names[8];
2169 transform_names[0] = "i";
2170 transform_names[1] = "r90";
2171 transform_names[2] = "r180";
2172 transform_names[3] = "r270";
2173 transform_names[4] = "fx";
2174 transform_names[6] = "fy";
2176 std::size_t pos = 0;
2177 while(pos < s.size())
2180 for (int i = 0; i <= 7; ++i)
2182 const std::string &name_i = transform_names[i];
2184 if (s[pos] == ('0' + i))
2191 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2193 pos += name_i.size();
2200 // Multiply total_transform and transform in the group D4
2203 new_total = (transform + total_transform) % 4;
2205 new_total = (transform - total_transform + 8) % 4;
2206 if ((transform >= 4) ^ (total_transform >= 4))
2209 total_transform = new_total;
2211 return total_transform;
2214 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2216 if (transform % 2 == 0)
2219 return core::dimension2d<u32>(dim.Height, dim.Width);
2222 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2224 if (src == NULL || dst == NULL)
2227 core::dimension2d<u32> dstdim = dst->getDimension();
2230 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2231 assert(transform <= 7);
2234 Compute the transformation from source coordinates (sx,sy)
2235 to destination coordinates (dx,dy).
2239 if (transform == 0) // identity
2240 sxn = 0, syn = 2; // sx = dx, sy = dy
2241 else if (transform == 1) // rotate by 90 degrees ccw
2242 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2243 else if (transform == 2) // rotate by 180 degrees
2244 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2245 else if (transform == 3) // rotate by 270 degrees ccw
2246 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2247 else if (transform == 4) // flip x
2248 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2249 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2250 sxn = 2, syn = 0; // sx = dy, sy = dx
2251 else if (transform == 6) // flip y
2252 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2253 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2254 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2256 for (u32 dy=0; dy<dstdim.Height; dy++)
2257 for (u32 dx=0; dx<dstdim.Width; dx++)
2259 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2260 u32 sx = entries[sxn];
2261 u32 sy = entries[syn];
2262 video::SColor c = src->getPixel(sx,sy);
2263 dst->setPixel(dx,dy,c);
2267 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2269 if (isKnownSourceImage("override_normal.png"))
2270 return getTexture("override_normal.png");
2271 std::string fname_base = name;
2272 static const char *normal_ext = "_normal.png";
2273 static const u32 normal_ext_size = strlen(normal_ext);
2274 size_t pos = fname_base.find('.');
2275 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2276 if (isKnownSourceImage(fname_normal)) {
2277 // look for image extension and replace it
2279 while ((i = fname_base.find('.', i)) != std::string::npos) {
2280 fname_base.replace(i, 4, normal_ext);
2281 i += normal_ext_size;
2283 return getTexture(fname_base);
2289 // For more colourspace transformations, see for example
2290 // https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
2292 inline float linear_to_srgb_component(float v)
2295 return 1.055f * powf(v, 1.0f / 2.4f) - 0.055f;
2298 inline float srgb_to_linear_component(float v)
2301 return powf((v + 0.055f) / 1.055f, 2.4f);
2305 v3f srgb_to_linear(const video::SColor &col_srgb)
2307 v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue());
2309 col.X = srgb_to_linear_component(col.X);
2310 col.Y = srgb_to_linear_component(col.Y);
2311 col.Z = srgb_to_linear_component(col.Z);
2315 video::SColor linear_to_srgb(const v3f &col_linear)
2318 col.X = linear_to_srgb_component(col_linear.X);
2319 col.Y = linear_to_srgb_component(col_linear.Y);
2320 col.Z = linear_to_srgb_component(col_linear.Z);
2322 col.X = core::clamp<float>(col.X, 0.0f, 255.0f);
2323 col.Y = core::clamp<float>(col.Y, 0.0f, 255.0f);
2324 col.Z = core::clamp<float>(col.Z, 0.0f, 255.0f);
2325 return video::SColor(0xff, myround(col.X), myround(col.Y),
2330 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2332 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2333 video::SColor c(0, 0, 0, 0);
2334 video::ITexture *texture = getTexture(name);
2337 video::IImage *image = driver->createImage(texture,
2338 core::position2d<s32>(0, 0),
2339 texture->getOriginalSize());
2344 v3f col_acc(0, 0, 0);
2345 core::dimension2d<u32> dim = image->getDimension();
2348 step = dim.Width / 16;
2349 for (u16 x = 0; x < dim.Width; x += step) {
2350 for (u16 y = 0; y < dim.Width; y += step) {
2351 c = image->getPixel(x,y);
2352 if (c.getAlpha() > 0) {
2354 col_acc += srgb_to_linear(c);
2361 c = linear_to_srgb(col_acc);
2368 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2370 std::string tname = "__shaderFlagsTexture";
2371 tname += normalmap_present ? "1" : "0";
2373 if (isKnownSourceImage(tname)) {
2374 return getTexture(tname);
2377 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2378 video::IImage *flags_image = driver->createImage(
2379 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2380 sanity_check(flags_image != NULL);
2381 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2382 flags_image->setPixel(0, 0, c);
2383 insertSourceImage(tname, flags_image);
2384 flags_image->drop();
2385 return getTexture(tname);
2389 std::vector<std::string> getTextureDirs()
2391 return fs::GetRecursiveDirs(g_settings->get("texture_path"));