3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <ICameraSceneNode.h>
24 #include <IrrCompileConfig.h>
25 #include "util/string.h"
26 #include "util/container.h"
27 #include "util/thread.h"
32 #include "util/strfnd.h"
33 #include "imagefilters.h"
34 #include "guiscalingfilter.h"
35 #include "renderingengine.h"
36 #include "util/base64.h"
39 A cache from texture name to texture path
41 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
44 Replaces the filename extension.
46 std::string image = "a/image.png"
47 replace_ext(image, "jpg")
48 -> image = "a/image.jpg"
49 Returns true on success.
51 static bool replace_ext(std::string &path, const char *ext)
55 // Find place of last dot, fail if \ or / found.
57 for (s32 i=path.size()-1; i>=0; i--)
65 if (path[i] == '\\' || path[i] == '/')
68 // If not found, return an empty string
71 // Else make the new path
72 path = path.substr(0, last_dot_i+1) + ext;
77 Find out the full path of an image by trying different filename
82 std::string getImagePath(std::string path)
84 // A NULL-ended list of possible image extensions
85 const char *extensions[] = { "png", "jpg", "bmp", "tga", NULL };
86 // If there is no extension, assume PNG
87 if (removeStringEnd(path, extensions).empty())
89 // Check paths until something is found to exist
90 const char **ext = extensions;
92 bool r = replace_ext(path, *ext);
95 if (fs::PathExists(path))
98 while((++ext) != NULL);
104 Gets the path to a texture by first checking if the texture exists
105 in texture_path and if not, using the data path.
107 Checks all supported extensions by replacing the original extension.
109 If not found, returns "".
111 Utilizes a thread-safe cache.
113 std::string getTexturePath(const std::string &filename, bool *is_base_pack)
115 std::string fullpath;
117 // This can set a wrong value on cached textures, but is irrelevant because
118 // is_base_pack is only passed when initializing the textures the first time
120 *is_base_pack = false;
124 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
129 Check from texture_path
131 for (const auto &path : getTextureDirs()) {
132 std::string testpath = path + DIR_DELIM;
133 testpath.append(filename);
134 // Check all filename extensions. Returns "" if not found.
135 fullpath = getImagePath(testpath);
136 if (!fullpath.empty())
141 Check from default data directory
143 if (fullpath.empty())
145 std::string base_path = porting::path_share + DIR_DELIM + "textures"
146 + DIR_DELIM + "base" + DIR_DELIM + "pack";
147 std::string testpath = base_path + DIR_DELIM + filename;
148 // Check all filename extensions. Returns "" if not found.
149 fullpath = getImagePath(testpath);
150 if (is_base_pack && !fullpath.empty())
151 *is_base_pack = true;
154 // Add to cache (also an empty result is cached)
155 g_texturename_to_path_cache.set(filename, fullpath);
161 void clearTextureNameCache()
163 g_texturename_to_path_cache.clear();
167 Stores internal information about a texture.
173 video::ITexture *texture;
174 std::set<std::string> sourceImages;
177 const std::string &name_,
178 video::ITexture *texture_=NULL
186 const std::string &name_,
187 video::ITexture *texture_,
188 std::set<std::string> &sourceImages_
192 sourceImages(sourceImages_)
198 SourceImageCache: A cache used for storing source images.
201 class SourceImageCache
204 ~SourceImageCache() {
205 for (auto &m_image : m_images) {
206 m_image.second->drop();
210 void insert(const std::string &name, video::IImage *img, bool prefer_local)
212 assert(img); // Pre-condition
214 std::map<std::string, video::IImage*>::iterator n;
215 n = m_images.find(name);
216 if (n != m_images.end()){
221 video::IImage* toadd = img;
222 bool need_to_grab = true;
224 // Try to use local texture instead if asked to
227 std::string path = getTexturePath(name, &is_base_pack);
229 if (!path.empty() && !is_base_pack) {
230 video::IImage *img2 = RenderingEngine::get_video_driver()->
231 createImageFromFile(path.c_str());
234 need_to_grab = false;
241 m_images[name] = toadd;
243 video::IImage* get(const std::string &name)
245 std::map<std::string, video::IImage*>::iterator n;
246 n = m_images.find(name);
247 if (n != m_images.end())
251 // Primarily fetches from cache, secondarily tries to read from filesystem
252 video::IImage *getOrLoad(const std::string &name)
254 std::map<std::string, video::IImage*>::iterator n;
255 n = m_images.find(name);
256 if (n != m_images.end()){
257 n->second->grab(); // Grab for caller
260 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
261 std::string path = getTexturePath(name);
263 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
264 <<name<<"\""<<std::endl;
267 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
269 video::IImage *img = driver->createImageFromFile(path.c_str());
272 m_images[name] = img;
273 img->grab(); // Grab for caller
278 std::map<std::string, video::IImage*> m_images;
285 class TextureSource : public IWritableTextureSource
289 virtual ~TextureSource();
293 Now, assume a texture with the id 1 exists, and has the name
294 "stone.png^mineral1".
295 Then a random thread calls getTextureId for a texture called
296 "stone.png^mineral1^crack0".
297 ...Now, WTF should happen? Well:
298 - getTextureId strips off stuff recursively from the end until
299 the remaining part is found, or nothing is left when
300 something is stripped out
302 But it is slow to search for textures by names and modify them
304 - ContentFeatures is made to contain ids for the basic plain
306 - Crack textures can be slow by themselves, but the framework
310 - Assume a texture with the id 1 exists, and has the name
311 "stone.png^mineral_coal.png".
312 - Now getNodeTile() stumbles upon a node which uses
313 texture id 1, and determines that MATERIAL_FLAG_CRACK
314 must be applied to the tile
315 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
316 has received the current crack level 0 from the client. It
317 finds out the name of the texture with getTextureName(1),
318 appends "^crack0" to it and gets a new texture id with
319 getTextureId("stone.png^mineral_coal.png^crack0").
324 Gets a texture id from cache or
325 - if main thread, generates the texture, adds to cache and returns id.
326 - if other thread, adds to request queue and waits for main thread.
328 The id 0 points to a NULL texture. It is returned in case of error.
330 u32 getTextureId(const std::string &name);
332 // Finds out the name of a cached texture.
333 std::string getTextureName(u32 id);
336 If texture specified by the name pointed by the id doesn't
337 exist, create it, then return the cached texture.
339 Can be called from any thread. If called from some other thread
340 and not found in cache, the call is queued to the main thread
343 video::ITexture* getTexture(u32 id);
345 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
348 Get a texture specifically intended for mesh
349 application, i.e. not HUD, compositing, or other 2D
350 use. This texture may be a different size and may
351 have had additional filters applied.
353 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
355 virtual Palette* getPalette(const std::string &name);
357 bool isKnownSourceImage(const std::string &name)
359 bool is_known = false;
360 bool cache_found = m_source_image_existence.get(name, &is_known);
363 // Not found in cache; find out if a local file exists
364 is_known = (!getTexturePath(name).empty());
365 m_source_image_existence.set(name, is_known);
369 // Processes queued texture requests from other threads.
370 // Shall be called from the main thread.
373 // Insert an image into the cache without touching the filesystem.
374 // Shall be called from the main thread.
375 void insertSourceImage(const std::string &name, video::IImage *img);
377 // Rebuild images and textures from the current set of source images
378 // Shall be called from the main thread.
379 void rebuildImagesAndTextures();
381 video::ITexture* getNormalTexture(const std::string &name);
382 video::SColor getTextureAverageColor(const std::string &name);
383 video::ITexture *getShaderFlagsTexture(bool normamap_present);
387 // The id of the thread that is allowed to use irrlicht directly
388 std::thread::id m_main_thread;
390 // Cache of source images
391 // This should be only accessed from the main thread
392 SourceImageCache m_sourcecache;
394 // Rebuild images and textures from the current set of source images
395 // Shall be called from the main thread.
396 // You ARE expected to be holding m_textureinfo_cache_mutex
397 void rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti);
399 // Generate a texture
400 u32 generateTexture(const std::string &name);
402 // Generate image based on a string like "stone.png" or "[crack:1:0".
403 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
404 // source_image_names is important to determine when to flush the image from a cache (dynamic media)
405 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg, std::set<std::string> &source_image_names);
407 /*! Generates an image from a full string like
408 * "stone.png^mineral_coal.png^[crack:1:0".
409 * Shall be called from the main thread.
410 * The returned Image should be dropped.
411 * source_image_names is important to determine when to flush the image from a cache (dynamic media)
413 video::IImage* generateImage(const std::string &name, std::set<std::string> &source_image_names);
415 // Thread-safe cache of what source images are known (true = known)
416 MutexedMap<std::string, bool> m_source_image_existence;
418 // A texture id is index in this array.
419 // The first position contains a NULL texture.
420 std::vector<TextureInfo> m_textureinfo_cache;
421 // Maps a texture name to an index in the former.
422 std::map<std::string, u32> m_name_to_id;
423 // The two former containers are behind this mutex
424 std::mutex m_textureinfo_cache_mutex;
426 // Queued texture fetches (to be processed by the main thread)
427 RequestQueue<std::string, u32, std::thread::id, u8> m_get_texture_queue;
429 // Textures that have been overwritten with other ones
430 // but can't be deleted because the ITexture* might still be used
431 std::vector<video::ITexture*> m_texture_trash;
433 // Maps image file names to loaded palettes.
434 std::unordered_map<std::string, Palette> m_palettes;
436 // Cached settings needed for making textures from meshes
437 bool m_setting_mipmap;
438 bool m_setting_trilinear_filter;
439 bool m_setting_bilinear_filter;
442 IWritableTextureSource *createTextureSource()
444 return new TextureSource();
447 TextureSource::TextureSource()
449 m_main_thread = std::this_thread::get_id();
451 // Add a NULL TextureInfo as the first index, named ""
452 m_textureinfo_cache.emplace_back("");
453 m_name_to_id[""] = 0;
455 // Cache some settings
456 // Note: Since this is only done once, the game must be restarted
457 // for these settings to take effect
458 m_setting_mipmap = g_settings->getBool("mip_map");
459 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
460 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
463 TextureSource::~TextureSource()
465 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
467 unsigned int textures_before = driver->getTextureCount();
469 for (const auto &iter : m_textureinfo_cache) {
472 driver->removeTexture(iter.texture);
474 m_textureinfo_cache.clear();
476 for (auto t : m_texture_trash) {
477 //cleanup trashed texture
478 driver->removeTexture(t);
481 infostream << "~TextureSource() before cleanup: "<< textures_before
482 << " after: " << driver->getTextureCount() << std::endl;
485 u32 TextureSource::getTextureId(const std::string &name)
487 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
491 See if texture already exists
493 MutexAutoLock lock(m_textureinfo_cache_mutex);
494 std::map<std::string, u32>::iterator n;
495 n = m_name_to_id.find(name);
496 if (n != m_name_to_id.end())
505 if (std::this_thread::get_id() == m_main_thread) {
506 return generateTexture(name);
510 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
512 // We're gonna ask the result to be put into here
513 static thread_local ResultQueue<std::string, u32, std::thread::id, u8> result_queue;
515 // Throw a request in
516 m_get_texture_queue.add(name, std::this_thread::get_id(), 0, &result_queue);
520 // Wait for result for up to 1 seconds (empirical value)
521 GetResult<std::string, u32, std::thread::id, u8>
522 result = result_queue.pop_front(1000);
524 if (result.key == name) {
528 } catch(ItemNotFoundException &e) {
529 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
533 infostream << "getTextureId(): Failed" << std::endl;
538 // Draw an image on top of another one, using the alpha channel of the
540 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
541 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
543 // Like blit_with_alpha, but only modifies destination pixels that
545 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
546 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
548 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
549 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
550 // color alpha with the destination alpha.
551 // Otherwise, any pixels that are not fully transparent get the color alpha.
552 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
553 const video::SColor &color, int ratio, bool keep_alpha);
555 // paint a texture using the given color
556 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
557 const video::SColor &color);
559 // Apply a mask to an image
560 static void apply_mask(video::IImage *mask, video::IImage *dst,
561 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
563 // Draw or overlay a crack
564 static void draw_crack(video::IImage *crack, video::IImage *dst,
565 bool use_overlay, s32 frame_count, s32 progression,
566 video::IVideoDriver *driver, u8 tiles = 1);
569 void brighten(video::IImage *image);
570 // Parse a transform name
571 u32 parseImageTransform(const std::string& s);
572 // Apply transform to image dimension
573 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
574 // Apply transform to image data
575 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
578 This method generates all the textures
580 u32 TextureSource::generateTexture(const std::string &name)
582 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
584 // Empty name means texture 0
586 infostream<<"generateTexture(): name is empty"<<std::endl;
592 See if texture already exists
594 MutexAutoLock lock(m_textureinfo_cache_mutex);
595 std::map<std::string, u32>::iterator n;
596 n = m_name_to_id.find(name);
597 if (n != m_name_to_id.end()) {
603 Calling only allowed from main thread
605 if (std::this_thread::get_id() != m_main_thread) {
606 errorstream<<"TextureSource::generateTexture() "
607 "called not from main thread"<<std::endl;
611 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
612 sanity_check(driver);
614 // passed into texture info for dynamic media tracking
615 std::set<std::string> source_image_names;
616 video::IImage *img = generateImage(name, source_image_names);
618 video::ITexture *tex = NULL;
622 img = Align2Npot2(img, driver);
624 // Create texture from resulting image
625 tex = driver->addTexture(name.c_str(), img);
626 guiScalingCache(io::path(name.c_str()), driver, img);
631 Add texture to caches (add NULL textures too)
634 MutexAutoLock lock(m_textureinfo_cache_mutex);
636 u32 id = m_textureinfo_cache.size();
637 TextureInfo ti(name, tex, source_image_names);
638 m_textureinfo_cache.push_back(ti);
639 m_name_to_id[name] = id;
644 std::string TextureSource::getTextureName(u32 id)
646 MutexAutoLock lock(m_textureinfo_cache_mutex);
648 if (id >= m_textureinfo_cache.size())
650 errorstream<<"TextureSource::getTextureName(): id="<<id
651 <<" >= m_textureinfo_cache.size()="
652 <<m_textureinfo_cache.size()<<std::endl;
656 return m_textureinfo_cache[id].name;
659 video::ITexture* TextureSource::getTexture(u32 id)
661 MutexAutoLock lock(m_textureinfo_cache_mutex);
663 if (id >= m_textureinfo_cache.size())
666 return m_textureinfo_cache[id].texture;
669 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
671 u32 actual_id = getTextureId(name);
675 return getTexture(actual_id);
678 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
680 static thread_local bool filter_needed =
681 g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
682 ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
683 g_settings->getS32("texture_min_size") > 1);
684 // Avoid duplicating texture if it won't actually change
686 return getTexture(name + "^[applyfiltersformesh", id);
687 return getTexture(name, id);
690 Palette* TextureSource::getPalette(const std::string &name)
692 // Only the main thread may load images
693 sanity_check(std::this_thread::get_id() == m_main_thread);
698 auto it = m_palettes.find(name);
699 if (it == m_palettes.end()) {
701 std::set<std::string> source_image_names; // unused, sadly.
702 video::IImage *img = generateImage(name, source_image_names);
704 warningstream << "TextureSource::getPalette(): palette \"" << name
705 << "\" could not be loaded." << std::endl;
709 u32 w = img->getDimension().Width;
710 u32 h = img->getDimension().Height;
711 // Real area of the image
716 warningstream << "TextureSource::getPalette(): the specified"
717 << " palette image \"" << name << "\" is larger than 256"
718 << " pixels, using the first 256." << std::endl;
720 } else if (256 % area != 0)
721 warningstream << "TextureSource::getPalette(): the "
722 << "specified palette image \"" << name << "\" does not "
723 << "contain power of two pixels." << std::endl;
724 // We stretch the palette so it will fit 256 values
725 // This many param2 values will have the same color
726 u32 step = 256 / area;
727 // For each pixel in the image
728 for (u32 i = 0; i < area; i++) {
729 video::SColor c = img->getPixel(i % w, i / w);
730 // Fill in palette with 'step' colors
731 for (u32 j = 0; j < step; j++)
732 new_palette.push_back(c);
735 // Fill in remaining elements
736 while (new_palette.size() < 256)
737 new_palette.emplace_back(0xFFFFFFFF);
738 m_palettes[name] = new_palette;
739 it = m_palettes.find(name);
741 if (it != m_palettes.end())
742 return &((*it).second);
746 void TextureSource::processQueue()
751 // NOTE: process outstanding requests from all mesh generation threads
752 while (!m_get_texture_queue.empty())
754 GetRequest<std::string, u32, std::thread::id, u8>
755 request = m_get_texture_queue.pop();
757 /*infostream<<"TextureSource::processQueue(): "
758 <<"got texture request with "
759 <<"name=\""<<request.key<<"\""
762 m_get_texture_queue.pushResult(request, generateTexture(request.key));
766 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
768 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
770 sanity_check(std::this_thread::get_id() == m_main_thread);
772 m_sourcecache.insert(name, img, true);
773 m_source_image_existence.set(name, true);
775 // now we need to check for any textures that need updating
776 MutexAutoLock lock(m_textureinfo_cache_mutex);
778 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
779 sanity_check(driver);
781 // Recreate affected textures
783 for (TextureInfo &ti : m_textureinfo_cache) {
785 continue; // Skip dummy entry
786 // If the source image was used, we need to rebuild this texture
787 if (ti.sourceImages.find(name) != ti.sourceImages.end()) {
788 rebuildTexture(driver, ti);
793 verbosestream << "TextureSource: inserting \"" << name << "\" caused rebuild of " << affected << " textures." << std::endl;
796 void TextureSource::rebuildImagesAndTextures()
798 MutexAutoLock lock(m_textureinfo_cache_mutex);
800 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
801 sanity_check(driver);
803 infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
804 << " textures" << std::endl;
807 for (TextureInfo &ti : m_textureinfo_cache) {
809 continue; // Skip dummy entry
810 rebuildTexture(driver, ti);
814 void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
817 return; // this shouldn't happen, just a precaution
819 // replaces the previous sourceImages
820 // shouldn't really need to be done, but can't hurt
821 std::set<std::string> source_image_names;
822 video::IImage *img = generateImage(ti.name, source_image_names);
824 img = Align2Npot2(img, driver);
826 // Create texture from resulting image
827 video::ITexture *t = NULL;
829 t = driver->addTexture(ti.name.c_str(), img);
830 guiScalingCache(io::path(ti.name.c_str()), driver, img);
833 video::ITexture *t_old = ti.texture;
836 ti.sourceImages = source_image_names;
839 m_texture_trash.push_back(t_old);
842 inline static void applyShadeFactor(video::SColor &color, u32 factor)
844 u32 f = core::clamp<u32>(factor, 0, 256);
845 color.setRed(color.getRed() * f / 256);
846 color.setGreen(color.getGreen() * f / 256);
847 color.setBlue(color.getBlue() * f / 256);
850 static video::IImage *createInventoryCubeImage(
851 video::IImage *top, video::IImage *left, video::IImage *right)
853 core::dimension2du size_top = top->getDimension();
854 core::dimension2du size_left = left->getDimension();
855 core::dimension2du size_right = right->getDimension();
857 u32 size = npot2(std::max({
858 size_top.Width, size_top.Height,
859 size_left.Width, size_left.Height,
860 size_right.Width, size_right.Height,
863 // It must be divisible by 4, to let everything work correctly.
864 // But it is a power of 2, so being at least 4 is the same.
865 // And the resulting texture should't be too large as well.
866 size = core::clamp<u32>(size, 4, 64);
868 // With such parameters, the cube fits exactly, touching each image line
869 // from `0` to `cube_size - 1`. (Note that division is exact here).
870 u32 cube_size = 9 * size;
871 u32 offset = size / 2;
873 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
875 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
877 core::dimension2du dim = image->getDimension();
878 video::ECOLOR_FORMAT format = image->getColorFormat();
879 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
880 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
881 image->copyToScaling(scaled);
885 sanity_check(image->getPitch() == 4 * size);
886 return reinterpret_cast<u32 *>(image->getData());
888 auto free_image = [] (video::IImage *image) -> void {
892 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
893 sanity_check(result->getPitch() == 4 * cube_size);
894 result->fill(video::SColor(0x00000000u));
895 u32 *target = reinterpret_cast<u32 *>(result->getData());
897 // Draws single cube face
898 // `shade_factor` is face brightness, in range [0.0, 1.0]
899 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
900 // `offsets` list pixels to be drawn for single source pixel
901 auto draw_image = [=] (video::IImage *image, float shade_factor,
902 s16 xu, s16 xv, s16 x1,
903 s16 yu, s16 yv, s16 y1,
904 std::initializer_list<v2s16> offsets) -> void {
905 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
906 const u32 *source = lock_image(image);
907 for (u16 v = 0; v < size; v++) {
908 for (u16 u = 0; u < size; u++) {
909 video::SColor pixel(*source);
910 applyShadeFactor(pixel, brightness);
911 s16 x = xu * u + xv * v + x1;
912 s16 y = yu * u + yv * v + y1;
913 for (const auto &off : offsets)
914 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
921 draw_image(top, 1.000000f,
922 4, -4, 4 * (size - 1),
925 {2, 0}, {3, 0}, {4, 0}, {5, 0},
926 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
927 {2, 2}, {3, 2}, {4, 2}, {5, 2},
930 draw_image(left, 0.836660f,
935 {0, 1}, {1, 1}, {2, 1}, {3, 1},
936 {0, 2}, {1, 2}, {2, 2}, {3, 2},
937 {0, 3}, {1, 3}, {2, 3}, {3, 3},
938 {0, 4}, {1, 4}, {2, 4}, {3, 4},
942 draw_image(right, 0.670820f,
947 {0, 1}, {1, 1}, {2, 1}, {3, 1},
948 {0, 2}, {1, 2}, {2, 2}, {3, 2},
949 {0, 3}, {1, 3}, {2, 3}, {3, 3},
950 {0, 4}, {1, 4}, {2, 4}, {3, 4},
957 video::IImage* TextureSource::generateImage(const std::string &name, std::set<std::string> &source_image_names)
959 // Get the base image
961 const char separator = '^';
962 const char escape = '\\';
963 const char paren_open = '(';
964 const char paren_close = ')';
966 // Find last separator in the name
967 s32 last_separator_pos = -1;
969 for (s32 i = name.size() - 1; i >= 0; i--) {
970 if (i > 0 && name[i-1] == escape)
974 if (paren_bal == 0) {
975 last_separator_pos = i;
976 i = -1; // break out of loop
980 if (paren_bal == 0) {
981 errorstream << "generateImage(): unbalanced parentheses"
982 << "(extranous '(') while generating texture \""
983 << name << "\"" << std::endl;
996 errorstream << "generateImage(): unbalanced parentheses"
997 << "(missing matching '(') while generating texture \""
998 << name << "\"" << std::endl;
1003 video::IImage *baseimg = NULL;
1006 If separator was found, make the base image
1007 using a recursive call.
1009 if (last_separator_pos != -1) {
1010 baseimg = generateImage(name.substr(0, last_separator_pos), source_image_names);
1014 Parse out the last part of the name of the image and act
1018 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1021 If this name is enclosed in parentheses, generate it
1022 and blit it onto the base image
1024 if (last_part_of_name[0] == paren_open
1025 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1026 std::string name2 = last_part_of_name.substr(1,
1027 last_part_of_name.size() - 2);
1028 video::IImage *tmp = generateImage(name2, source_image_names);
1030 errorstream << "generateImage(): "
1031 "Failed to generate \"" << name2 << "\""
1037 core::dimension2d<u32> dim = tmp->getDimension();
1038 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1043 } else if (!generateImagePart(last_part_of_name, baseimg, source_image_names)) {
1044 // Generate image according to part of name
1045 errorstream << "generateImage(): "
1046 "Failed to generate \"" << last_part_of_name << "\""
1050 // If no resulting image, print a warning
1051 if (baseimg == NULL) {
1052 errorstream << "generateImage(): baseimg is NULL (attempted to"
1053 " create texture \"" << name << "\")" << std::endl;
1062 * Check and align image to npot2 if required by hardware
1063 * @param image image to check for npot2 alignment
1064 * @param driver driver to use for image operations
1065 * @return image or copy of image aligned to npot2
1067 video::IImage *Align2Npot2(video::IImage *image,
1068 video::IVideoDriver *driver)
1073 if (driver->queryFeature(video::EVDF_TEXTURE_NPOT))
1076 core::dimension2d<u32> dim = image->getDimension();
1077 unsigned int height = npot2(dim.Height);
1078 unsigned int width = npot2(dim.Width);
1080 if (dim.Height == height && dim.Width == width)
1083 if (dim.Height > height)
1085 if (dim.Width > width)
1088 video::IImage *targetimage =
1089 driver->createImage(video::ECF_A8R8G8B8,
1090 core::dimension2d<u32>(width, height));
1092 if (targetimage != NULL)
1093 image->copyToScaling(targetimage);
1100 static std::string unescape_string(const std::string &str, const char esc = '\\')
1103 size_t pos = 0, cpos;
1104 out.reserve(str.size());
1106 cpos = str.find_first_of(esc, pos);
1107 if (cpos == std::string::npos) {
1108 out += str.substr(pos);
1111 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1117 void blitBaseImage(video::IImage* &src, video::IImage* &dst)
1119 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1120 // Size of the copied area
1121 core::dimension2d<u32> dim = src->getDimension();
1122 //core::dimension2d<u32> dim(16,16);
1123 // Position to copy the blitted to in the base image
1124 core::position2d<s32> pos_to(0,0);
1125 // Position to copy the blitted from in the blitted image
1126 core::position2d<s32> pos_from(0,0);
1128 /*image->copyToWithAlpha(baseimg, pos_to,
1129 core::rect<s32>(pos_from, dim),
1130 video::SColor(255,255,255,255),
1133 core::dimension2d<u32> dim_dst = dst->getDimension();
1134 if (dim == dim_dst) {
1135 blit_with_alpha(src, dst, pos_from, pos_to, dim);
1136 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1137 // Upscale overlying image
1138 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1139 createImage(video::ECF_A8R8G8B8, dim_dst);
1140 src->copyToScaling(scaled_image);
1142 blit_with_alpha(scaled_image, dst, pos_from, pos_to, dim_dst);
1143 scaled_image->drop();
1145 // Upscale base image
1146 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1147 createImage(video::ECF_A8R8G8B8, dim);
1148 dst->copyToScaling(scaled_base);
1152 blit_with_alpha(src, dst, pos_from, pos_to, dim);
1156 bool TextureSource::generateImagePart(std::string part_of_name,
1157 video::IImage *& baseimg, std::set<std::string> &source_image_names)
1159 const char escape = '\\'; // same as in generateImage()
1160 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1161 sanity_check(driver);
1163 // Stuff starting with [ are special commands
1164 if (part_of_name.empty() || part_of_name[0] != '[') {
1165 source_image_names.insert(part_of_name);
1166 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1167 if (image == NULL) {
1168 if (!part_of_name.empty()) {
1170 // Do not create normalmap dummies
1171 if (part_of_name.find("_normal.png") != std::string::npos) {
1172 warningstream << "generateImage(): Could not load normal map \""
1173 << part_of_name << "\"" << std::endl;
1177 errorstream << "generateImage(): Could not load image \""
1178 << part_of_name << "\" while building texture; "
1179 "Creating a dummy image" << std::endl;
1182 // Just create a dummy image
1183 //core::dimension2d<u32> dim(2,2);
1184 core::dimension2d<u32> dim(1,1);
1185 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1186 sanity_check(image != NULL);
1187 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1188 image->setPixel(1,0, video::SColor(255,0,255,0));
1189 image->setPixel(0,1, video::SColor(255,0,0,255));
1190 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1191 image->setPixel(0,0, video::SColor(255,myrand()%256,
1192 myrand()%256,myrand()%256));
1193 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1194 myrand()%256,myrand()%256));
1195 image->setPixel(0,1, video::SColor(255,myrand()%256,
1196 myrand()%256,myrand()%256));
1197 image->setPixel(1,1, video::SColor(255,myrand()%256,
1198 myrand()%256,myrand()%256));*/
1201 // If base image is NULL, load as base.
1202 if (baseimg == NULL)
1204 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1206 Copy it this way to get an alpha channel.
1207 Otherwise images with alpha cannot be blitted on
1208 images that don't have alpha in the original file.
1210 core::dimension2d<u32> dim = image->getDimension();
1211 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1212 image->copyTo(baseimg);
1214 // Else blit on base.
1217 blitBaseImage(image, baseimg);
1224 // A special texture modification
1226 /*infostream<<"generateImage(): generating special "
1227 <<"modification \""<<part_of_name<<"\""
1233 Adds a cracking texture
1234 N = animation frame count, P = crack progression
1236 if (str_starts_with(part_of_name, "[crack"))
1238 if (baseimg == NULL) {
1239 errorstream<<"generateImagePart(): baseimg == NULL "
1240 <<"for part_of_name=\""<<part_of_name
1241 <<"\", cancelling."<<std::endl;
1245 // Crack image number and overlay option
1246 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1247 bool use_overlay = (part_of_name[6] == 'o');
1248 Strfnd sf(part_of_name);
1250 s32 frame_count = stoi(sf.next(":"));
1251 s32 progression = stoi(sf.next(":"));
1253 // Check whether there is the <tiles> argument, that is,
1254 // whether there are 3 arguments. If so, shift values
1255 // as the first and not the last argument is optional.
1256 auto s = sf.next(":");
1258 tiles = frame_count;
1259 frame_count = progression;
1260 progression = stoi(s);
1263 if (progression >= 0) {
1267 It is an image with a number of cracking stages
1270 video::IImage *img_crack = m_sourcecache.getOrLoad(
1271 "crack_anylength.png");
1274 draw_crack(img_crack, baseimg,
1275 use_overlay, frame_count,
1276 progression, driver, tiles);
1282 [combine:WxH:X,Y=filename:X,Y=filename2
1283 Creates a bigger texture from any amount of smaller ones
1285 else if (str_starts_with(part_of_name, "[combine"))
1287 Strfnd sf(part_of_name);
1289 u32 w0 = stoi(sf.next("x"));
1290 u32 h0 = stoi(sf.next(":"));
1291 core::dimension2d<u32> dim(w0,h0);
1292 if (baseimg == NULL) {
1293 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1294 baseimg->fill(video::SColor(0,0,0,0));
1296 while (!sf.at_end()) {
1297 u32 x = stoi(sf.next(","));
1298 u32 y = stoi(sf.next("="));
1299 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1300 infostream<<"Adding \""<<filename
1301 <<"\" to combined ("<<x<<","<<y<<")"
1303 video::IImage *img = generateImage(filename, source_image_names);
1305 core::dimension2d<u32> dim = img->getDimension();
1306 core::position2d<s32> pos_base(x, y);
1307 video::IImage *img2 =
1308 driver->createImage(video::ECF_A8R8G8B8, dim);
1311 /*img2->copyToWithAlpha(baseimg, pos_base,
1312 core::rect<s32>(v2s32(0,0), dim),
1313 video::SColor(255,255,255,255),
1315 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1318 errorstream << "generateImagePart(): Failed to load image \""
1319 << filename << "\" for [combine" << std::endl;
1326 else if (str_starts_with(part_of_name, "[brighten"))
1328 if (baseimg == NULL) {
1329 errorstream<<"generateImagePart(): baseimg==NULL "
1330 <<"for part_of_name=\""<<part_of_name
1331 <<"\", cancelling."<<std::endl;
1339 Make image completely opaque.
1340 Used for the leaves texture when in old leaves mode, so
1341 that the transparent parts don't look completely black
1342 when simple alpha channel is used for rendering.
1344 else if (str_starts_with(part_of_name, "[noalpha"))
1346 if (baseimg == NULL){
1347 errorstream<<"generateImagePart(): baseimg==NULL "
1348 <<"for part_of_name=\""<<part_of_name
1349 <<"\", cancelling."<<std::endl;
1353 core::dimension2d<u32> dim = baseimg->getDimension();
1355 // Set alpha to full
1356 for (u32 y=0; y<dim.Height; y++)
1357 for (u32 x=0; x<dim.Width; x++)
1359 video::SColor c = baseimg->getPixel(x,y);
1361 baseimg->setPixel(x,y,c);
1366 Convert one color to transparent.
1368 else if (str_starts_with(part_of_name, "[makealpha:"))
1370 if (baseimg == NULL) {
1371 errorstream<<"generateImagePart(): baseimg == NULL "
1372 <<"for part_of_name=\""<<part_of_name
1373 <<"\", cancelling."<<std::endl;
1377 Strfnd sf(part_of_name.substr(11));
1378 u32 r1 = stoi(sf.next(","));
1379 u32 g1 = stoi(sf.next(","));
1380 u32 b1 = stoi(sf.next(""));
1382 core::dimension2d<u32> dim = baseimg->getDimension();
1384 /*video::IImage *oldbaseimg = baseimg;
1385 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1386 oldbaseimg->copyTo(baseimg);
1387 oldbaseimg->drop();*/
1389 // Set alpha to full
1390 for (u32 y=0; y<dim.Height; y++)
1391 for (u32 x=0; x<dim.Width; x++)
1393 video::SColor c = baseimg->getPixel(x,y);
1395 u32 g = c.getGreen();
1396 u32 b = c.getBlue();
1397 if (!(r == r1 && g == g1 && b == b1))
1400 baseimg->setPixel(x,y,c);
1405 Rotates and/or flips the image.
1407 N can be a number (between 0 and 7) or a transform name.
1408 Rotations are counter-clockwise.
1410 1 R90 rotate by 90 degrees
1411 2 R180 rotate by 180 degrees
1412 3 R270 rotate by 270 degrees
1414 5 FXR90 flip X then rotate by 90 degrees
1416 7 FYR90 flip Y then rotate by 90 degrees
1418 Note: Transform names can be concatenated to produce
1419 their product (applies the first then the second).
1420 The resulting transform will be equivalent to one of the
1421 eight existing ones, though (see: dihedral group).
1423 else if (str_starts_with(part_of_name, "[transform"))
1425 if (baseimg == NULL) {
1426 errorstream<<"generateImagePart(): baseimg == NULL "
1427 <<"for part_of_name=\""<<part_of_name
1428 <<"\", cancelling."<<std::endl;
1432 u32 transform = parseImageTransform(part_of_name.substr(10));
1433 core::dimension2d<u32> dim = imageTransformDimension(
1434 transform, baseimg->getDimension());
1435 video::IImage *image = driver->createImage(
1436 baseimg->getColorFormat(), dim);
1437 sanity_check(image != NULL);
1438 imageTransform(transform, baseimg, image);
1443 [inventorycube{topimage{leftimage{rightimage
1444 In every subimage, replace ^ with &.
1445 Create an "inventory cube".
1446 NOTE: This should be used only on its own.
1447 Example (a grass block (not actually used in game):
1448 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1450 else if (str_starts_with(part_of_name, "[inventorycube"))
1452 if (baseimg != NULL){
1453 errorstream<<"generateImagePart(): baseimg != NULL "
1454 <<"for part_of_name=\""<<part_of_name
1455 <<"\", cancelling."<<std::endl;
1459 str_replace(part_of_name, '&', '^');
1460 Strfnd sf(part_of_name);
1462 std::string imagename_top = sf.next("{");
1463 std::string imagename_left = sf.next("{");
1464 std::string imagename_right = sf.next("{");
1466 // Generate images for the faces of the cube
1467 video::IImage *img_top = generateImage(imagename_top, source_image_names);
1468 video::IImage *img_left = generateImage(imagename_left, source_image_names);
1469 video::IImage *img_right = generateImage(imagename_right, source_image_names);
1471 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1472 errorstream << "generateImagePart(): Failed to create textures"
1473 << " for inventorycube \"" << part_of_name << "\""
1475 baseimg = generateImage(imagename_top, source_image_names);
1479 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1481 // Face images are not needed anymore
1489 [lowpart:percent:filename
1490 Adds the lower part of a texture
1492 else if (str_starts_with(part_of_name, "[lowpart:"))
1494 Strfnd sf(part_of_name);
1496 u32 percent = stoi(sf.next(":"));
1497 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1499 if (baseimg == NULL)
1500 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1501 video::IImage *img = generateImage(filename, source_image_names);
1504 core::dimension2d<u32> dim = img->getDimension();
1505 core::position2d<s32> pos_base(0, 0);
1506 video::IImage *img2 =
1507 driver->createImage(video::ECF_A8R8G8B8, dim);
1510 core::position2d<s32> clippos(0, 0);
1511 clippos.Y = dim.Height * (100-percent) / 100;
1512 core::dimension2d<u32> clipdim = dim;
1513 clipdim.Height = clipdim.Height * percent / 100 + 1;
1514 core::rect<s32> cliprect(clippos, clipdim);
1515 img2->copyToWithAlpha(baseimg, pos_base,
1516 core::rect<s32>(v2s32(0,0), dim),
1517 video::SColor(255,255,255,255),
1524 Crops a frame of a vertical animation.
1525 N = frame count, I = frame index
1527 else if (str_starts_with(part_of_name, "[verticalframe:"))
1529 Strfnd sf(part_of_name);
1531 u32 frame_count = stoi(sf.next(":"));
1532 u32 frame_index = stoi(sf.next(":"));
1534 if (baseimg == NULL){
1535 errorstream<<"generateImagePart(): baseimg != NULL "
1536 <<"for part_of_name=\""<<part_of_name
1537 <<"\", cancelling."<<std::endl;
1541 v2u32 frame_size = baseimg->getDimension();
1542 frame_size.Y /= frame_count;
1544 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1547 errorstream<<"generateImagePart(): Could not create image "
1548 <<"for part_of_name=\""<<part_of_name
1549 <<"\", cancelling."<<std::endl;
1553 // Fill target image with transparency
1554 img->fill(video::SColor(0,0,0,0));
1556 core::dimension2d<u32> dim = frame_size;
1557 core::position2d<s32> pos_dst(0, 0);
1558 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1559 baseimg->copyToWithAlpha(img, pos_dst,
1560 core::rect<s32>(pos_src, dim),
1561 video::SColor(255,255,255,255),
1569 Applies a mask to an image
1571 else if (str_starts_with(part_of_name, "[mask:"))
1573 if (baseimg == NULL) {
1574 errorstream << "generateImage(): baseimg == NULL "
1575 << "for part_of_name=\"" << part_of_name
1576 << "\", cancelling." << std::endl;
1579 Strfnd sf(part_of_name);
1581 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1583 video::IImage *img = generateImage(filename, source_image_names);
1585 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1586 img->getDimension());
1589 errorstream << "generateImage(): Failed to load \""
1590 << filename << "\".";
1595 multiplys a given color to any pixel of an image
1596 color = color as ColorString
1598 else if (str_starts_with(part_of_name, "[multiply:")) {
1599 Strfnd sf(part_of_name);
1601 std::string color_str = sf.next(":");
1603 if (baseimg == NULL) {
1604 errorstream << "generateImagePart(): baseimg != NULL "
1605 << "for part_of_name=\"" << part_of_name
1606 << "\", cancelling." << std::endl;
1610 video::SColor color;
1612 if (!parseColorString(color_str, color, false))
1615 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1619 Overlays image with given color
1620 color = color as ColorString
1622 else if (str_starts_with(part_of_name, "[colorize:"))
1624 Strfnd sf(part_of_name);
1626 std::string color_str = sf.next(":");
1627 std::string ratio_str = sf.next(":");
1629 if (baseimg == NULL) {
1630 errorstream << "generateImagePart(): baseimg != NULL "
1631 << "for part_of_name=\"" << part_of_name
1632 << "\", cancelling." << std::endl;
1636 video::SColor color;
1638 bool keep_alpha = false;
1640 if (!parseColorString(color_str, color, false))
1643 if (is_number(ratio_str))
1644 ratio = mystoi(ratio_str, 0, 255);
1645 else if (ratio_str == "alpha")
1648 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1651 [applyfiltersformesh
1654 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1656 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1660 errorstream << "generateImagePart(): baseimg == NULL "
1661 << "for part_of_name=\"" << part_of_name
1662 << "\", cancelling." << std::endl;
1666 // Apply the "clean transparent" filter, if needed
1667 if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
1668 imageCleanTransparent(baseimg, 127);
1670 /* Upscale textures to user's requested minimum size. This is a trick to make
1671 * filters look as good on low-res textures as on high-res ones, by making
1672 * low-res textures BECOME high-res ones. This is helpful for worlds that
1673 * mix high- and low-res textures, or for mods with least-common-denominator
1674 * textures that don't have the resources to offer high-res alternatives.
1676 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1677 const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1;
1679 const core::dimension2d<u32> dim = baseimg->getDimension();
1681 /* Calculate scaling needed to make the shortest texture dimension
1682 * equal to the target minimum. If e.g. this is a vertical frames
1683 * animation, the short dimension will be the real size.
1685 if ((dim.Width == 0) || (dim.Height == 0)) {
1686 errorstream << "generateImagePart(): Illegal 0 dimension "
1687 << "for part_of_name=\""<< part_of_name
1688 << "\", cancelling." << std::endl;
1691 u32 xscale = scaleto / dim.Width;
1692 u32 yscale = scaleto / dim.Height;
1693 u32 scale = (xscale > yscale) ? xscale : yscale;
1695 // Never downscale; only scale up by 2x or more.
1697 u32 w = scale * dim.Width;
1698 u32 h = scale * dim.Height;
1699 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1700 video::IImage *newimg = driver->createImage(
1701 baseimg->getColorFormat(), newdim);
1702 baseimg->copyToScaling(newimg);
1710 Resizes the base image to the given dimensions
1712 else if (str_starts_with(part_of_name, "[resize"))
1714 if (baseimg == NULL) {
1715 errorstream << "generateImagePart(): baseimg == NULL "
1716 << "for part_of_name=\""<< part_of_name
1717 << "\", cancelling." << std::endl;
1721 Strfnd sf(part_of_name);
1723 u32 width = stoi(sf.next("x"));
1724 u32 height = stoi(sf.next(""));
1725 core::dimension2d<u32> dim(width, height);
1727 video::IImage *image = RenderingEngine::get_video_driver()->
1728 createImage(video::ECF_A8R8G8B8, dim);
1729 baseimg->copyToScaling(image);
1735 Makes the base image transparent according to the given ratio.
1736 R must be between 0 and 255.
1737 0 means totally transparent.
1738 255 means totally opaque.
1740 else if (str_starts_with(part_of_name, "[opacity:")) {
1741 if (baseimg == NULL) {
1742 errorstream << "generateImagePart(): baseimg == NULL "
1743 << "for part_of_name=\"" << part_of_name
1744 << "\", cancelling." << std::endl;
1748 Strfnd sf(part_of_name);
1751 u32 ratio = mystoi(sf.next(""), 0, 255);
1753 core::dimension2d<u32> dim = baseimg->getDimension();
1755 for (u32 y = 0; y < dim.Height; y++)
1756 for (u32 x = 0; x < dim.Width; x++)
1758 video::SColor c = baseimg->getPixel(x, y);
1759 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1760 baseimg->setPixel(x, y, c);
1765 Inverts the given channels of the base image.
1766 Mode may contain the characters "r", "g", "b", "a".
1767 Only the channels that are mentioned in the mode string
1770 else if (str_starts_with(part_of_name, "[invert:")) {
1771 if (baseimg == NULL) {
1772 errorstream << "generateImagePart(): baseimg == NULL "
1773 << "for part_of_name=\"" << part_of_name
1774 << "\", cancelling." << std::endl;
1778 Strfnd sf(part_of_name);
1781 std::string mode = sf.next("");
1783 if (mode.find('a') != std::string::npos)
1784 mask |= 0xff000000UL;
1785 if (mode.find('r') != std::string::npos)
1786 mask |= 0x00ff0000UL;
1787 if (mode.find('g') != std::string::npos)
1788 mask |= 0x0000ff00UL;
1789 if (mode.find('b') != std::string::npos)
1790 mask |= 0x000000ffUL;
1792 core::dimension2d<u32> dim = baseimg->getDimension();
1794 for (u32 y = 0; y < dim.Height; y++)
1795 for (u32 x = 0; x < dim.Width; x++)
1797 video::SColor c = baseimg->getPixel(x, y);
1799 baseimg->setPixel(x, y, c);
1804 Retrieves a tile at position X,Y (in tiles)
1805 from the base image it assumes to be a
1806 tilesheet with dimensions W,H (in tiles).
1808 else if (part_of_name.substr(0,7) == "[sheet:") {
1809 if (baseimg == NULL) {
1810 errorstream << "generateImagePart(): baseimg != NULL "
1811 << "for part_of_name=\"" << part_of_name
1812 << "\", cancelling." << std::endl;
1816 Strfnd sf(part_of_name);
1818 u32 w0 = stoi(sf.next("x"));
1819 u32 h0 = stoi(sf.next(":"));
1820 u32 x0 = stoi(sf.next(","));
1821 u32 y0 = stoi(sf.next(":"));
1823 core::dimension2d<u32> img_dim = baseimg->getDimension();
1824 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1826 video::IImage *img = driver->createImage(
1827 video::ECF_A8R8G8B8, tile_dim);
1829 errorstream << "generateImagePart(): Could not create image "
1830 << "for part_of_name=\"" << part_of_name
1831 << "\", cancelling." << std::endl;
1835 img->fill(video::SColor(0,0,0,0));
1836 v2u32 vdim(tile_dim);
1837 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1838 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1839 video::SColor(255,255,255,255), NULL);
1847 Decodes a PNG image in base64 form.
1848 Use minetest.encode_png and minetest.encode_base64
1849 to produce a valid string.
1851 else if (str_starts_with(part_of_name, "[png:")) {
1852 Strfnd sf(part_of_name);
1856 std::string blob = sf.next("");
1857 if (!base64_is_valid(blob)) {
1858 errorstream << "generateImagePart(): "
1859 << "malformed base64 in '[png'"
1863 png = base64_decode(blob);
1866 auto *device = RenderingEngine::get_raw_device();
1867 auto *fs = device->getFileSystem();
1868 auto *vd = device->getVideoDriver();
1869 auto *memfile = fs->createMemoryReadFile(png.data(), png.size(), "__temp_png");
1870 video::IImage* pngimg = vd->createImageFromFile(memfile);
1874 blitBaseImage(pngimg, baseimg);
1876 core::dimension2d<u32> dim = pngimg->getDimension();
1877 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1878 pngimg->copyTo(baseimg);
1884 errorstream << "generateImagePart(): Invalid "
1885 " modification: \"" << part_of_name << "\"" << std::endl;
1893 Calculate the color of a single pixel drawn on top of another pixel.
1895 This is a little more complicated than just video::SColor::getInterpolated
1896 because getInterpolated does not handle alpha correctly. For example, a
1897 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1898 pixel with alpha=160, while getInterpolated would yield alpha=96.
1900 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1902 if (dst_c.getAlpha() == 0)
1904 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1905 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1906 src_c.getAlpha() * ratio / (255 * 255));
1911 Draw an image on top of another one, using the alpha channel of the
1914 This exists because IImage::copyToWithAlpha() doesn't seem to always
1917 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1918 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1920 for (u32 y0=0; y0<size.Y; y0++)
1921 for (u32 x0=0; x0<size.X; x0++)
1923 s32 src_x = src_pos.X + x0;
1924 s32 src_y = src_pos.Y + y0;
1925 s32 dst_x = dst_pos.X + x0;
1926 s32 dst_y = dst_pos.Y + y0;
1927 video::SColor src_c = src->getPixel(src_x, src_y);
1928 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1929 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1930 dst->setPixel(dst_x, dst_y, dst_c);
1935 Draw an image on top of another one, using the alpha channel of the
1936 source image; only modify fully opaque pixels in destinaion
1938 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1939 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1941 for (u32 y0=0; y0<size.Y; y0++)
1942 for (u32 x0=0; x0<size.X; x0++)
1944 s32 src_x = src_pos.X + x0;
1945 s32 src_y = src_pos.Y + y0;
1946 s32 dst_x = dst_pos.X + x0;
1947 s32 dst_y = dst_pos.Y + y0;
1948 video::SColor src_c = src->getPixel(src_x, src_y);
1949 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1950 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1952 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1953 dst->setPixel(dst_x, dst_y, dst_c);
1958 // This function has been disabled because it is currently unused.
1959 // Feel free to re-enable if you find it handy.
1962 Draw an image on top of another one, using the specified ratio
1963 modify all partially-opaque pixels in the destination.
1965 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1966 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1968 for (u32 y0 = 0; y0 < size.Y; y0++)
1969 for (u32 x0 = 0; x0 < size.X; x0++)
1971 s32 src_x = src_pos.X + x0;
1972 s32 src_y = src_pos.Y + y0;
1973 s32 dst_x = dst_pos.X + x0;
1974 s32 dst_y = dst_pos.Y + y0;
1975 video::SColor src_c = src->getPixel(src_x, src_y);
1976 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1977 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1980 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1982 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1983 dst->setPixel(dst_x, dst_y, dst_c);
1990 Apply color to destination
1992 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1993 const video::SColor &color, int ratio, bool keep_alpha)
1995 u32 alpha = color.getAlpha();
1996 video::SColor dst_c;
1997 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1998 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2000 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2001 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2002 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2003 if (dst_alpha > 0) {
2004 dst_c.setAlpha(dst_alpha * alpha / 255);
2005 dst->setPixel(x, y, dst_c);
2008 } else { // replace the color including the alpha
2009 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2010 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2011 if (dst->getPixel(x, y).getAlpha() > 0)
2012 dst->setPixel(x, y, color);
2014 } else { // interpolate between the color and destination
2015 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2016 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2017 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2018 dst_c = dst->getPixel(x, y);
2019 if (dst_c.getAlpha() > 0) {
2020 dst_c = color.getInterpolated(dst_c, interp);
2021 dst->setPixel(x, y, dst_c);
2028 Apply color to destination
2030 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2031 const video::SColor &color)
2033 video::SColor dst_c;
2035 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2036 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2037 dst_c = dst->getPixel(x, y);
2040 (dst_c.getRed() * color.getRed()) / 255,
2041 (dst_c.getGreen() * color.getGreen()) / 255,
2042 (dst_c.getBlue() * color.getBlue()) / 255
2044 dst->setPixel(x, y, dst_c);
2049 Apply mask to destination
2051 static void apply_mask(video::IImage *mask, video::IImage *dst,
2052 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2054 for (u32 y0 = 0; y0 < size.Y; y0++) {
2055 for (u32 x0 = 0; x0 < size.X; x0++) {
2056 s32 mask_x = x0 + mask_pos.X;
2057 s32 mask_y = y0 + mask_pos.Y;
2058 s32 dst_x = x0 + dst_pos.X;
2059 s32 dst_y = y0 + dst_pos.Y;
2060 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2061 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2062 dst_c.color &= mask_c.color;
2063 dst->setPixel(dst_x, dst_y, dst_c);
2068 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2069 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2071 core::dimension2d<u32> strip_size = crack->getDimension();
2072 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2073 core::dimension2d<u32> tile_size(size / tiles);
2074 s32 frame_count = strip_size.Height / strip_size.Width;
2075 if (frame_index >= frame_count)
2076 frame_index = frame_count - 1;
2077 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2078 video::IImage *result = nullptr;
2080 // extract crack frame
2081 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2084 if (tile_size == frame_size) {
2085 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2087 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2089 goto exit__has_tile;
2090 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2091 crack_frame->copyToScaling(crack_tile);
2092 crack_frame->drop();
2098 result = driver->createImage(video::ECF_A8R8G8B8, size);
2100 goto exit__has_tile;
2102 for (u8 i = 0; i < tiles; i++)
2103 for (u8 j = 0; j < tiles; j++)
2104 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2111 static void draw_crack(video::IImage *crack, video::IImage *dst,
2112 bool use_overlay, s32 frame_count, s32 progression,
2113 video::IVideoDriver *driver, u8 tiles)
2115 // Dimension of destination image
2116 core::dimension2d<u32> dim_dst = dst->getDimension();
2117 // Limit frame_count
2118 if (frame_count > (s32) dim_dst.Height)
2119 frame_count = dim_dst.Height;
2120 if (frame_count < 1)
2122 // Dimension of the scaled crack stage,
2123 // which is the same as the dimension of a single destination frame
2124 core::dimension2d<u32> frame_size(
2126 dim_dst.Height / frame_count
2128 video::IImage *crack_scaled = create_crack_image(crack, progression,
2129 frame_size, tiles, driver);
2133 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2134 for (s32 i = 0; i < frame_count; ++i) {
2135 v2s32 dst_pos(0, frame_size.Height * i);
2136 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2139 crack_scaled->drop();
2142 void brighten(video::IImage *image)
2147 core::dimension2d<u32> dim = image->getDimension();
2149 for (u32 y=0; y<dim.Height; y++)
2150 for (u32 x=0; x<dim.Width; x++)
2152 video::SColor c = image->getPixel(x,y);
2153 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2154 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2155 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2156 image->setPixel(x,y,c);
2160 u32 parseImageTransform(const std::string& s)
2162 int total_transform = 0;
2164 std::string transform_names[8];
2165 transform_names[0] = "i";
2166 transform_names[1] = "r90";
2167 transform_names[2] = "r180";
2168 transform_names[3] = "r270";
2169 transform_names[4] = "fx";
2170 transform_names[6] = "fy";
2172 std::size_t pos = 0;
2173 while(pos < s.size())
2176 for (int i = 0; i <= 7; ++i)
2178 const std::string &name_i = transform_names[i];
2180 if (s[pos] == ('0' + i))
2187 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2189 pos += name_i.size();
2196 // Multiply total_transform and transform in the group D4
2199 new_total = (transform + total_transform) % 4;
2201 new_total = (transform - total_transform + 8) % 4;
2202 if ((transform >= 4) ^ (total_transform >= 4))
2205 total_transform = new_total;
2207 return total_transform;
2210 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2212 if (transform % 2 == 0)
2215 return core::dimension2d<u32>(dim.Height, dim.Width);
2218 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2220 if (src == NULL || dst == NULL)
2223 core::dimension2d<u32> dstdim = dst->getDimension();
2226 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2227 assert(transform <= 7);
2230 Compute the transformation from source coordinates (sx,sy)
2231 to destination coordinates (dx,dy).
2235 if (transform == 0) // identity
2236 sxn = 0, syn = 2; // sx = dx, sy = dy
2237 else if (transform == 1) // rotate by 90 degrees ccw
2238 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2239 else if (transform == 2) // rotate by 180 degrees
2240 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2241 else if (transform == 3) // rotate by 270 degrees ccw
2242 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2243 else if (transform == 4) // flip x
2244 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2245 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2246 sxn = 2, syn = 0; // sx = dy, sy = dx
2247 else if (transform == 6) // flip y
2248 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2249 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2250 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2252 for (u32 dy=0; dy<dstdim.Height; dy++)
2253 for (u32 dx=0; dx<dstdim.Width; dx++)
2255 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2256 u32 sx = entries[sxn];
2257 u32 sy = entries[syn];
2258 video::SColor c = src->getPixel(sx,sy);
2259 dst->setPixel(dx,dy,c);
2263 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2265 if (isKnownSourceImage("override_normal.png"))
2266 return getTexture("override_normal.png");
2267 std::string fname_base = name;
2268 static const char *normal_ext = "_normal.png";
2269 static const u32 normal_ext_size = strlen(normal_ext);
2270 size_t pos = fname_base.find('.');
2271 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2272 if (isKnownSourceImage(fname_normal)) {
2273 // look for image extension and replace it
2275 while ((i = fname_base.find('.', i)) != std::string::npos) {
2276 fname_base.replace(i, 4, normal_ext);
2277 i += normal_ext_size;
2279 return getTexture(fname_base);
2285 // For more colourspace transformations, see for example
2286 // https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
2288 inline float linear_to_srgb_component(float v)
2291 return 1.055f * powf(v, 1.0f / 2.4f) - 0.055f;
2294 inline float srgb_to_linear_component(float v)
2297 return powf((v + 0.055f) / 1.055f, 2.4f);
2301 v3f srgb_to_linear(const video::SColor &col_srgb)
2303 v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue());
2305 col.X = srgb_to_linear_component(col.X);
2306 col.Y = srgb_to_linear_component(col.Y);
2307 col.Z = srgb_to_linear_component(col.Z);
2311 video::SColor linear_to_srgb(const v3f &col_linear)
2314 col.X = linear_to_srgb_component(col_linear.X);
2315 col.Y = linear_to_srgb_component(col_linear.Y);
2316 col.Z = linear_to_srgb_component(col_linear.Z);
2318 col.X = core::clamp<float>(col.X, 0.0f, 255.0f);
2319 col.Y = core::clamp<float>(col.Y, 0.0f, 255.0f);
2320 col.Z = core::clamp<float>(col.Z, 0.0f, 255.0f);
2321 return video::SColor(0xff, myround(col.X), myround(col.Y),
2326 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2328 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2329 video::SColor c(0, 0, 0, 0);
2330 video::ITexture *texture = getTexture(name);
2333 video::IImage *image = driver->createImage(texture,
2334 core::position2d<s32>(0, 0),
2335 texture->getOriginalSize());
2340 v3f col_acc(0, 0, 0);
2341 core::dimension2d<u32> dim = image->getDimension();
2344 step = dim.Width / 16;
2345 for (u16 x = 0; x < dim.Width; x += step) {
2346 for (u16 y = 0; y < dim.Width; y += step) {
2347 c = image->getPixel(x,y);
2348 if (c.getAlpha() > 0) {
2350 col_acc += srgb_to_linear(c);
2357 c = linear_to_srgb(col_acc);
2364 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2366 std::string tname = "__shaderFlagsTexture";
2367 tname += normalmap_present ? "1" : "0";
2369 if (isKnownSourceImage(tname)) {
2370 return getTexture(tname);
2373 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2374 video::IImage *flags_image = driver->createImage(
2375 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2376 sanity_check(flags_image != NULL);
2377 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2378 flags_image->setPixel(0, 0, c);
2379 insertSourceImage(tname, flags_image);
2380 flags_image->drop();
2381 return getTexture(tname);
2385 std::vector<std::string> getTextureDirs()
2387 return fs::GetRecursiveDirs(g_settings->get("texture_path"));