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;
176 const std::string &name_,
177 video::ITexture *texture_=NULL
186 SourceImageCache: A cache used for storing source images.
189 class SourceImageCache
192 ~SourceImageCache() {
193 for (auto &m_image : m_images) {
194 m_image.second->drop();
198 void insert(const std::string &name, video::IImage *img, bool prefer_local)
200 assert(img); // Pre-condition
202 std::map<std::string, video::IImage*>::iterator n;
203 n = m_images.find(name);
204 if (n != m_images.end()){
209 video::IImage* toadd = img;
210 bool need_to_grab = true;
212 // Try to use local texture instead if asked to
215 std::string path = getTexturePath(name, &is_base_pack);
217 if (!path.empty() && !is_base_pack) {
218 video::IImage *img2 = RenderingEngine::get_video_driver()->
219 createImageFromFile(path.c_str());
222 need_to_grab = false;
229 m_images[name] = toadd;
231 video::IImage* get(const std::string &name)
233 std::map<std::string, video::IImage*>::iterator n;
234 n = m_images.find(name);
235 if (n != m_images.end())
239 // Primarily fetches from cache, secondarily tries to read from filesystem
240 video::IImage *getOrLoad(const std::string &name)
242 std::map<std::string, video::IImage*>::iterator n;
243 n = m_images.find(name);
244 if (n != m_images.end()){
245 n->second->grab(); // Grab for caller
248 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
249 std::string path = getTexturePath(name);
251 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
252 <<name<<"\""<<std::endl;
255 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
257 video::IImage *img = driver->createImageFromFile(path.c_str());
260 m_images[name] = img;
261 img->grab(); // Grab for caller
266 std::map<std::string, video::IImage*> m_images;
273 class TextureSource : public IWritableTextureSource
277 virtual ~TextureSource();
281 Now, assume a texture with the id 1 exists, and has the name
282 "stone.png^mineral1".
283 Then a random thread calls getTextureId for a texture called
284 "stone.png^mineral1^crack0".
285 ...Now, WTF should happen? Well:
286 - getTextureId strips off stuff recursively from the end until
287 the remaining part is found, or nothing is left when
288 something is stripped out
290 But it is slow to search for textures by names and modify them
292 - ContentFeatures is made to contain ids for the basic plain
294 - Crack textures can be slow by themselves, but the framework
298 - Assume a texture with the id 1 exists, and has the name
299 "stone.png^mineral_coal.png".
300 - Now getNodeTile() stumbles upon a node which uses
301 texture id 1, and determines that MATERIAL_FLAG_CRACK
302 must be applied to the tile
303 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
304 has received the current crack level 0 from the client. It
305 finds out the name of the texture with getTextureName(1),
306 appends "^crack0" to it and gets a new texture id with
307 getTextureId("stone.png^mineral_coal.png^crack0").
312 Gets a texture id from cache or
313 - if main thread, generates the texture, adds to cache and returns id.
314 - if other thread, adds to request queue and waits for main thread.
316 The id 0 points to a NULL texture. It is returned in case of error.
318 u32 getTextureId(const std::string &name);
320 // Finds out the name of a cached texture.
321 std::string getTextureName(u32 id);
324 If texture specified by the name pointed by the id doesn't
325 exist, create it, then return the cached texture.
327 Can be called from any thread. If called from some other thread
328 and not found in cache, the call is queued to the main thread
331 video::ITexture* getTexture(u32 id);
333 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
336 Get a texture specifically intended for mesh
337 application, i.e. not HUD, compositing, or other 2D
338 use. This texture may be a different size and may
339 have had additional filters applied.
341 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
343 virtual Palette* getPalette(const std::string &name);
345 bool isKnownSourceImage(const std::string &name)
347 bool is_known = false;
348 bool cache_found = m_source_image_existence.get(name, &is_known);
351 // Not found in cache; find out if a local file exists
352 is_known = (!getTexturePath(name).empty());
353 m_source_image_existence.set(name, is_known);
357 // Processes queued texture requests from other threads.
358 // Shall be called from the main thread.
361 // Insert an image into the cache without touching the filesystem.
362 // Shall be called from the main thread.
363 void insertSourceImage(const std::string &name, video::IImage *img);
365 // Rebuild images and textures from the current set of source images
366 // Shall be called from the main thread.
367 void rebuildImagesAndTextures();
369 video::ITexture* getNormalTexture(const std::string &name);
370 video::SColor getTextureAverageColor(const std::string &name);
371 video::ITexture *getShaderFlagsTexture(bool normamap_present);
375 // The id of the thread that is allowed to use irrlicht directly
376 std::thread::id m_main_thread;
378 // Cache of source images
379 // This should be only accessed from the main thread
380 SourceImageCache m_sourcecache;
382 // Generate a texture
383 u32 generateTexture(const std::string &name);
385 // Generate image based on a string like "stone.png" or "[crack:1:0".
386 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
387 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
389 /*! Generates an image from a full string like
390 * "stone.png^mineral_coal.png^[crack:1:0".
391 * Shall be called from the main thread.
392 * The returned Image should be dropped.
394 video::IImage* generateImage(const std::string &name);
396 // Thread-safe cache of what source images are known (true = known)
397 MutexedMap<std::string, bool> m_source_image_existence;
399 // A texture id is index in this array.
400 // The first position contains a NULL texture.
401 std::vector<TextureInfo> m_textureinfo_cache;
402 // Maps a texture name to an index in the former.
403 std::map<std::string, u32> m_name_to_id;
404 // The two former containers are behind this mutex
405 std::mutex m_textureinfo_cache_mutex;
407 // Queued texture fetches (to be processed by the main thread)
408 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
410 // Textures that have been overwritten with other ones
411 // but can't be deleted because the ITexture* might still be used
412 std::vector<video::ITexture*> m_texture_trash;
414 // Maps image file names to loaded palettes.
415 std::unordered_map<std::string, Palette> m_palettes;
417 // Cached settings needed for making textures from meshes
418 bool m_setting_mipmap;
419 bool m_setting_trilinear_filter;
420 bool m_setting_bilinear_filter;
423 IWritableTextureSource *createTextureSource()
425 return new TextureSource();
428 TextureSource::TextureSource()
430 m_main_thread = std::this_thread::get_id();
432 // Add a NULL TextureInfo as the first index, named ""
433 m_textureinfo_cache.emplace_back("");
434 m_name_to_id[""] = 0;
436 // Cache some settings
437 // Note: Since this is only done once, the game must be restarted
438 // for these settings to take effect
439 m_setting_mipmap = g_settings->getBool("mip_map");
440 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
441 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
444 TextureSource::~TextureSource()
446 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
448 unsigned int textures_before = driver->getTextureCount();
450 for (const auto &iter : m_textureinfo_cache) {
453 driver->removeTexture(iter.texture);
455 m_textureinfo_cache.clear();
457 for (auto t : m_texture_trash) {
458 //cleanup trashed texture
459 driver->removeTexture(t);
462 infostream << "~TextureSource() before cleanup: "<< textures_before
463 << " after: " << driver->getTextureCount() << std::endl;
466 u32 TextureSource::getTextureId(const std::string &name)
468 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
472 See if texture already exists
474 MutexAutoLock lock(m_textureinfo_cache_mutex);
475 std::map<std::string, u32>::iterator n;
476 n = m_name_to_id.find(name);
477 if (n != m_name_to_id.end())
486 if (std::this_thread::get_id() == m_main_thread) {
487 return generateTexture(name);
491 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
493 // We're gonna ask the result to be put into here
494 static ResultQueue<std::string, u32, u8, u8> result_queue;
496 // Throw a request in
497 m_get_texture_queue.add(name, 0, 0, &result_queue);
501 // Wait result for a second
502 GetResult<std::string, u32, u8, u8>
503 result = result_queue.pop_front(1000);
505 if (result.key == name) {
509 } catch(ItemNotFoundException &e) {
510 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
514 infostream << "getTextureId(): Failed" << std::endl;
519 // Draw an image on top of an another one, using the alpha channel of the
521 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
522 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
524 // Like blit_with_alpha, but only modifies destination pixels that
526 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
527 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
529 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
530 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
531 // color alpha with the destination alpha.
532 // Otherwise, any pixels that are not fully transparent get the color alpha.
533 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
534 const video::SColor &color, int ratio, bool keep_alpha);
536 // paint a texture using the given color
537 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
538 const video::SColor &color);
540 // Apply a mask to an image
541 static void apply_mask(video::IImage *mask, video::IImage *dst,
542 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
544 // Draw or overlay a crack
545 static void draw_crack(video::IImage *crack, video::IImage *dst,
546 bool use_overlay, s32 frame_count, s32 progression,
547 video::IVideoDriver *driver, u8 tiles = 1);
550 void brighten(video::IImage *image);
551 // Parse a transform name
552 u32 parseImageTransform(const std::string& s);
553 // Apply transform to image dimension
554 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
555 // Apply transform to image data
556 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
559 This method generates all the textures
561 u32 TextureSource::generateTexture(const std::string &name)
563 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
565 // Empty name means texture 0
567 infostream<<"generateTexture(): name is empty"<<std::endl;
573 See if texture already exists
575 MutexAutoLock lock(m_textureinfo_cache_mutex);
576 std::map<std::string, u32>::iterator n;
577 n = m_name_to_id.find(name);
578 if (n != m_name_to_id.end()) {
584 Calling only allowed from main thread
586 if (std::this_thread::get_id() != m_main_thread) {
587 errorstream<<"TextureSource::generateTexture() "
588 "called not from main thread"<<std::endl;
592 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
593 sanity_check(driver);
595 video::IImage *img = generateImage(name);
597 video::ITexture *tex = NULL;
601 img = Align2Npot2(img, driver);
603 // Create texture from resulting image
604 tex = driver->addTexture(name.c_str(), img);
605 guiScalingCache(io::path(name.c_str()), driver, img);
610 Add texture to caches (add NULL textures too)
613 MutexAutoLock lock(m_textureinfo_cache_mutex);
615 u32 id = m_textureinfo_cache.size();
616 TextureInfo ti(name, tex);
617 m_textureinfo_cache.push_back(ti);
618 m_name_to_id[name] = id;
623 std::string TextureSource::getTextureName(u32 id)
625 MutexAutoLock lock(m_textureinfo_cache_mutex);
627 if (id >= m_textureinfo_cache.size())
629 errorstream<<"TextureSource::getTextureName(): id="<<id
630 <<" >= m_textureinfo_cache.size()="
631 <<m_textureinfo_cache.size()<<std::endl;
635 return m_textureinfo_cache[id].name;
638 video::ITexture* TextureSource::getTexture(u32 id)
640 MutexAutoLock lock(m_textureinfo_cache_mutex);
642 if (id >= m_textureinfo_cache.size())
645 return m_textureinfo_cache[id].texture;
648 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
650 u32 actual_id = getTextureId(name);
654 return getTexture(actual_id);
657 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
659 static thread_local bool filter_needed =
660 g_settings->getBool("texture_clean_transparent") || m_setting_mipmap ||
661 ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
662 g_settings->getS32("texture_min_size") > 1);
663 // Avoid duplicating texture if it won't actually change
665 return getTexture(name + "^[applyfiltersformesh", id);
666 return getTexture(name, id);
669 Palette* TextureSource::getPalette(const std::string &name)
671 // Only the main thread may load images
672 sanity_check(std::this_thread::get_id() == m_main_thread);
677 auto it = m_palettes.find(name);
678 if (it == m_palettes.end()) {
680 video::IImage *img = generateImage(name);
682 warningstream << "TextureSource::getPalette(): palette \"" << name
683 << "\" could not be loaded." << std::endl;
687 u32 w = img->getDimension().Width;
688 u32 h = img->getDimension().Height;
689 // Real area of the image
694 warningstream << "TextureSource::getPalette(): the specified"
695 << " palette image \"" << name << "\" is larger than 256"
696 << " pixels, using the first 256." << std::endl;
698 } else if (256 % area != 0)
699 warningstream << "TextureSource::getPalette(): the "
700 << "specified palette image \"" << name << "\" does not "
701 << "contain power of two pixels." << std::endl;
702 // We stretch the palette so it will fit 256 values
703 // This many param2 values will have the same color
704 u32 step = 256 / area;
705 // For each pixel in the image
706 for (u32 i = 0; i < area; i++) {
707 video::SColor c = img->getPixel(i % w, i / w);
708 // Fill in palette with 'step' colors
709 for (u32 j = 0; j < step; j++)
710 new_palette.push_back(c);
713 // Fill in remaining elements
714 while (new_palette.size() < 256)
715 new_palette.emplace_back(0xFFFFFFFF);
716 m_palettes[name] = new_palette;
717 it = m_palettes.find(name);
719 if (it != m_palettes.end())
720 return &((*it).second);
724 void TextureSource::processQueue()
729 //NOTE this is only thread safe for ONE consumer thread!
730 if (!m_get_texture_queue.empty())
732 GetRequest<std::string, u32, u8, u8>
733 request = m_get_texture_queue.pop();
735 /*infostream<<"TextureSource::processQueue(): "
736 <<"got texture request with "
737 <<"name=\""<<request.key<<"\""
740 m_get_texture_queue.pushResult(request, generateTexture(request.key));
744 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
746 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
748 sanity_check(std::this_thread::get_id() == m_main_thread);
750 m_sourcecache.insert(name, img, true);
751 m_source_image_existence.set(name, true);
754 void TextureSource::rebuildImagesAndTextures()
756 MutexAutoLock lock(m_textureinfo_cache_mutex);
758 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
759 sanity_check(driver);
761 infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
762 << " textures" << std::endl;
765 for (TextureInfo &ti : m_textureinfo_cache) {
767 continue; // Skip dummy entry
769 video::IImage *img = generateImage(ti.name);
771 img = Align2Npot2(img, driver);
773 // Create texture from resulting image
774 video::ITexture *t = NULL;
776 t = driver->addTexture(ti.name.c_str(), img);
777 guiScalingCache(io::path(ti.name.c_str()), driver, img);
780 video::ITexture *t_old = ti.texture;
785 m_texture_trash.push_back(t_old);
789 inline static void applyShadeFactor(video::SColor &color, u32 factor)
791 u32 f = core::clamp<u32>(factor, 0, 256);
792 color.setRed(color.getRed() * f / 256);
793 color.setGreen(color.getGreen() * f / 256);
794 color.setBlue(color.getBlue() * f / 256);
797 static video::IImage *createInventoryCubeImage(
798 video::IImage *top, video::IImage *left, video::IImage *right)
800 core::dimension2du size_top = top->getDimension();
801 core::dimension2du size_left = left->getDimension();
802 core::dimension2du size_right = right->getDimension();
804 u32 size = npot2(std::max({
805 size_top.Width, size_top.Height,
806 size_left.Width, size_left.Height,
807 size_right.Width, size_right.Height,
810 // It must be divisible by 4, to let everything work correctly.
811 // But it is a power of 2, so being at least 4 is the same.
812 // And the resulting texture should't be too large as well.
813 size = core::clamp<u32>(size, 4, 64);
815 // With such parameters, the cube fits exactly, touching each image line
816 // from `0` to `cube_size - 1`. (Note that division is exact here).
817 u32 cube_size = 9 * size;
818 u32 offset = size / 2;
820 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
822 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
824 core::dimension2du dim = image->getDimension();
825 video::ECOLOR_FORMAT format = image->getColorFormat();
826 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
827 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
828 image->copyToScaling(scaled);
832 sanity_check(image->getPitch() == 4 * size);
833 return reinterpret_cast<u32 *>(image->getData());
835 auto free_image = [] (video::IImage *image) -> void {
839 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
840 sanity_check(result->getPitch() == 4 * cube_size);
841 result->fill(video::SColor(0x00000000u));
842 u32 *target = reinterpret_cast<u32 *>(result->getData());
844 // Draws single cube face
845 // `shade_factor` is face brightness, in range [0.0, 1.0]
846 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
847 // `offsets` list pixels to be drawn for single source pixel
848 auto draw_image = [=] (video::IImage *image, float shade_factor,
849 s16 xu, s16 xv, s16 x1,
850 s16 yu, s16 yv, s16 y1,
851 std::initializer_list<v2s16> offsets) -> void {
852 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
853 const u32 *source = lock_image(image);
854 for (u16 v = 0; v < size; v++) {
855 for (u16 u = 0; u < size; u++) {
856 video::SColor pixel(*source);
857 applyShadeFactor(pixel, brightness);
858 s16 x = xu * u + xv * v + x1;
859 s16 y = yu * u + yv * v + y1;
860 for (const auto &off : offsets)
861 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
868 draw_image(top, 1.000000f,
869 4, -4, 4 * (size - 1),
872 {2, 0}, {3, 0}, {4, 0}, {5, 0},
873 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
874 {2, 2}, {3, 2}, {4, 2}, {5, 2},
877 draw_image(left, 0.836660f,
882 {0, 1}, {1, 1}, {2, 1}, {3, 1},
883 {0, 2}, {1, 2}, {2, 2}, {3, 2},
884 {0, 3}, {1, 3}, {2, 3}, {3, 3},
885 {0, 4}, {1, 4}, {2, 4}, {3, 4},
889 draw_image(right, 0.670820f,
894 {0, 1}, {1, 1}, {2, 1}, {3, 1},
895 {0, 2}, {1, 2}, {2, 2}, {3, 2},
896 {0, 3}, {1, 3}, {2, 3}, {3, 3},
897 {0, 4}, {1, 4}, {2, 4}, {3, 4},
904 video::IImage* TextureSource::generateImage(const std::string &name)
906 // Get the base image
908 const char separator = '^';
909 const char escape = '\\';
910 const char paren_open = '(';
911 const char paren_close = ')';
913 // Find last separator in the name
914 s32 last_separator_pos = -1;
916 for (s32 i = name.size() - 1; i >= 0; i--) {
917 if (i > 0 && name[i-1] == escape)
921 if (paren_bal == 0) {
922 last_separator_pos = i;
923 i = -1; // break out of loop
927 if (paren_bal == 0) {
928 errorstream << "generateImage(): unbalanced parentheses"
929 << "(extranous '(') while generating texture \""
930 << name << "\"" << std::endl;
943 errorstream << "generateImage(): unbalanced parentheses"
944 << "(missing matching '(') while generating texture \""
945 << name << "\"" << std::endl;
950 video::IImage *baseimg = NULL;
953 If separator was found, make the base image
954 using a recursive call.
956 if (last_separator_pos != -1) {
957 baseimg = generateImage(name.substr(0, last_separator_pos));
961 Parse out the last part of the name of the image and act
965 std::string last_part_of_name = name.substr(last_separator_pos + 1);
968 If this name is enclosed in parentheses, generate it
969 and blit it onto the base image
971 if (last_part_of_name[0] == paren_open
972 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
973 std::string name2 = last_part_of_name.substr(1,
974 last_part_of_name.size() - 2);
975 video::IImage *tmp = generateImage(name2);
977 errorstream << "generateImage(): "
978 "Failed to generate \"" << name2 << "\""
984 core::dimension2d<u32> dim = tmp->getDimension();
985 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
990 } else if (!generateImagePart(last_part_of_name, baseimg)) {
991 // Generate image according to part of name
992 errorstream << "generateImage(): "
993 "Failed to generate \"" << last_part_of_name << "\""
997 // If no resulting image, print a warning
998 if (baseimg == NULL) {
999 errorstream << "generateImage(): baseimg is NULL (attempted to"
1000 " create texture \"" << name << "\")" << std::endl;
1009 * Check and align image to npot2 if required by hardware
1010 * @param image image to check for npot2 alignment
1011 * @param driver driver to use for image operations
1012 * @return image or copy of image aligned to npot2
1014 video::IImage *Align2Npot2(video::IImage *image,
1015 video::IVideoDriver *driver)
1020 if (driver->queryFeature(video::EVDF_TEXTURE_NPOT))
1023 core::dimension2d<u32> dim = image->getDimension();
1024 unsigned int height = npot2(dim.Height);
1025 unsigned int width = npot2(dim.Width);
1027 if (dim.Height == height && dim.Width == width)
1030 if (dim.Height > height)
1032 if (dim.Width > width)
1035 video::IImage *targetimage =
1036 driver->createImage(video::ECF_A8R8G8B8,
1037 core::dimension2d<u32>(width, height));
1039 if (targetimage != NULL)
1040 image->copyToScaling(targetimage);
1047 static std::string unescape_string(const std::string &str, const char esc = '\\')
1050 size_t pos = 0, cpos;
1051 out.reserve(str.size());
1053 cpos = str.find_first_of(esc, pos);
1054 if (cpos == std::string::npos) {
1055 out += str.substr(pos);
1058 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1064 void blitBaseImage(video::IImage* &src, video::IImage* &dst)
1066 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1067 // Size of the copied area
1068 core::dimension2d<u32> dim = src->getDimension();
1069 //core::dimension2d<u32> dim(16,16);
1070 // Position to copy the blitted to in the base image
1071 core::position2d<s32> pos_to(0,0);
1072 // Position to copy the blitted from in the blitted image
1073 core::position2d<s32> pos_from(0,0);
1075 /*image->copyToWithAlpha(baseimg, pos_to,
1076 core::rect<s32>(pos_from, dim),
1077 video::SColor(255,255,255,255),
1080 core::dimension2d<u32> dim_dst = dst->getDimension();
1081 if (dim == dim_dst) {
1082 blit_with_alpha(src, dst, pos_from, pos_to, dim);
1083 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1084 // Upscale overlying image
1085 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1086 createImage(video::ECF_A8R8G8B8, dim_dst);
1087 src->copyToScaling(scaled_image);
1089 blit_with_alpha(scaled_image, dst, pos_from, pos_to, dim_dst);
1090 scaled_image->drop();
1092 // Upscale base image
1093 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1094 createImage(video::ECF_A8R8G8B8, dim);
1095 dst->copyToScaling(scaled_base);
1099 blit_with_alpha(src, dst, pos_from, pos_to, dim);
1103 bool TextureSource::generateImagePart(std::string part_of_name,
1104 video::IImage *& baseimg)
1106 const char escape = '\\'; // same as in generateImage()
1107 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1108 sanity_check(driver);
1110 // Stuff starting with [ are special commands
1111 if (part_of_name.empty() || part_of_name[0] != '[') {
1112 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1113 if (image == NULL) {
1114 if (!part_of_name.empty()) {
1116 // Do not create normalmap dummies
1117 if (part_of_name.find("_normal.png") != std::string::npos) {
1118 warningstream << "generateImage(): Could not load normal map \""
1119 << part_of_name << "\"" << std::endl;
1123 errorstream << "generateImage(): Could not load image \""
1124 << part_of_name << "\" while building texture; "
1125 "Creating a dummy image" << std::endl;
1128 // Just create a dummy image
1129 //core::dimension2d<u32> dim(2,2);
1130 core::dimension2d<u32> dim(1,1);
1131 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1132 sanity_check(image != NULL);
1133 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1134 image->setPixel(1,0, video::SColor(255,0,255,0));
1135 image->setPixel(0,1, video::SColor(255,0,0,255));
1136 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1137 image->setPixel(0,0, video::SColor(255,myrand()%256,
1138 myrand()%256,myrand()%256));
1139 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1140 myrand()%256,myrand()%256));
1141 image->setPixel(0,1, video::SColor(255,myrand()%256,
1142 myrand()%256,myrand()%256));
1143 image->setPixel(1,1, video::SColor(255,myrand()%256,
1144 myrand()%256,myrand()%256));*/
1147 // If base image is NULL, load as base.
1148 if (baseimg == NULL)
1150 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1152 Copy it this way to get an alpha channel.
1153 Otherwise images with alpha cannot be blitted on
1154 images that don't have alpha in the original file.
1156 core::dimension2d<u32> dim = image->getDimension();
1157 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1158 image->copyTo(baseimg);
1160 // Else blit on base.
1163 blitBaseImage(image, baseimg);
1170 // A special texture modification
1172 /*infostream<<"generateImage(): generating special "
1173 <<"modification \""<<part_of_name<<"\""
1179 Adds a cracking texture
1180 N = animation frame count, P = crack progression
1182 if (str_starts_with(part_of_name, "[crack"))
1184 if (baseimg == NULL) {
1185 errorstream<<"generateImagePart(): baseimg == NULL "
1186 <<"for part_of_name=\""<<part_of_name
1187 <<"\", cancelling."<<std::endl;
1191 // Crack image number and overlay option
1192 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1193 bool use_overlay = (part_of_name[6] == 'o');
1194 Strfnd sf(part_of_name);
1196 s32 frame_count = stoi(sf.next(":"));
1197 s32 progression = stoi(sf.next(":"));
1199 // Check whether there is the <tiles> argument, that is,
1200 // whether there are 3 arguments. If so, shift values
1201 // as the first and not the last argument is optional.
1202 auto s = sf.next(":");
1204 tiles = frame_count;
1205 frame_count = progression;
1206 progression = stoi(s);
1209 if (progression >= 0) {
1213 It is an image with a number of cracking stages
1216 video::IImage *img_crack = m_sourcecache.getOrLoad(
1217 "crack_anylength.png");
1220 draw_crack(img_crack, baseimg,
1221 use_overlay, frame_count,
1222 progression, driver, tiles);
1228 [combine:WxH:X,Y=filename:X,Y=filename2
1229 Creates a bigger texture from any amount of smaller ones
1231 else if (str_starts_with(part_of_name, "[combine"))
1233 Strfnd sf(part_of_name);
1235 u32 w0 = stoi(sf.next("x"));
1236 u32 h0 = stoi(sf.next(":"));
1237 core::dimension2d<u32> dim(w0,h0);
1238 if (baseimg == NULL) {
1239 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1240 baseimg->fill(video::SColor(0,0,0,0));
1242 while (!sf.at_end()) {
1243 u32 x = stoi(sf.next(","));
1244 u32 y = stoi(sf.next("="));
1245 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1246 infostream<<"Adding \""<<filename
1247 <<"\" to combined ("<<x<<","<<y<<")"
1249 video::IImage *img = generateImage(filename);
1251 core::dimension2d<u32> dim = img->getDimension();
1252 core::position2d<s32> pos_base(x, y);
1253 video::IImage *img2 =
1254 driver->createImage(video::ECF_A8R8G8B8, dim);
1257 /*img2->copyToWithAlpha(baseimg, pos_base,
1258 core::rect<s32>(v2s32(0,0), dim),
1259 video::SColor(255,255,255,255),
1261 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1264 errorstream << "generateImagePart(): Failed to load image \""
1265 << filename << "\" for [combine" << std::endl;
1272 else if (str_starts_with(part_of_name, "[brighten"))
1274 if (baseimg == NULL) {
1275 errorstream<<"generateImagePart(): baseimg==NULL "
1276 <<"for part_of_name=\""<<part_of_name
1277 <<"\", cancelling."<<std::endl;
1285 Make image completely opaque.
1286 Used for the leaves texture when in old leaves mode, so
1287 that the transparent parts don't look completely black
1288 when simple alpha channel is used for rendering.
1290 else if (str_starts_with(part_of_name, "[noalpha"))
1292 if (baseimg == NULL){
1293 errorstream<<"generateImagePart(): baseimg==NULL "
1294 <<"for part_of_name=\""<<part_of_name
1295 <<"\", cancelling."<<std::endl;
1299 core::dimension2d<u32> dim = baseimg->getDimension();
1301 // Set alpha to full
1302 for (u32 y=0; y<dim.Height; y++)
1303 for (u32 x=0; x<dim.Width; x++)
1305 video::SColor c = baseimg->getPixel(x,y);
1307 baseimg->setPixel(x,y,c);
1312 Convert one color to transparent.
1314 else if (str_starts_with(part_of_name, "[makealpha:"))
1316 if (baseimg == NULL) {
1317 errorstream<<"generateImagePart(): baseimg == NULL "
1318 <<"for part_of_name=\""<<part_of_name
1319 <<"\", cancelling."<<std::endl;
1323 Strfnd sf(part_of_name.substr(11));
1324 u32 r1 = stoi(sf.next(","));
1325 u32 g1 = stoi(sf.next(","));
1326 u32 b1 = stoi(sf.next(""));
1328 core::dimension2d<u32> dim = baseimg->getDimension();
1330 /*video::IImage *oldbaseimg = baseimg;
1331 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1332 oldbaseimg->copyTo(baseimg);
1333 oldbaseimg->drop();*/
1335 // Set alpha to full
1336 for (u32 y=0; y<dim.Height; y++)
1337 for (u32 x=0; x<dim.Width; x++)
1339 video::SColor c = baseimg->getPixel(x,y);
1341 u32 g = c.getGreen();
1342 u32 b = c.getBlue();
1343 if (!(r == r1 && g == g1 && b == b1))
1346 baseimg->setPixel(x,y,c);
1351 Rotates and/or flips the image.
1353 N can be a number (between 0 and 7) or a transform name.
1354 Rotations are counter-clockwise.
1356 1 R90 rotate by 90 degrees
1357 2 R180 rotate by 180 degrees
1358 3 R270 rotate by 270 degrees
1360 5 FXR90 flip X then rotate by 90 degrees
1362 7 FYR90 flip Y then rotate by 90 degrees
1364 Note: Transform names can be concatenated to produce
1365 their product (applies the first then the second).
1366 The resulting transform will be equivalent to one of the
1367 eight existing ones, though (see: dihedral group).
1369 else if (str_starts_with(part_of_name, "[transform"))
1371 if (baseimg == NULL) {
1372 errorstream<<"generateImagePart(): baseimg == NULL "
1373 <<"for part_of_name=\""<<part_of_name
1374 <<"\", cancelling."<<std::endl;
1378 u32 transform = parseImageTransform(part_of_name.substr(10));
1379 core::dimension2d<u32> dim = imageTransformDimension(
1380 transform, baseimg->getDimension());
1381 video::IImage *image = driver->createImage(
1382 baseimg->getColorFormat(), dim);
1383 sanity_check(image != NULL);
1384 imageTransform(transform, baseimg, image);
1389 [inventorycube{topimage{leftimage{rightimage
1390 In every subimage, replace ^ with &.
1391 Create an "inventory cube".
1392 NOTE: This should be used only on its own.
1393 Example (a grass block (not actually used in game):
1394 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1396 else if (str_starts_with(part_of_name, "[inventorycube"))
1398 if (baseimg != NULL){
1399 errorstream<<"generateImagePart(): baseimg != NULL "
1400 <<"for part_of_name=\""<<part_of_name
1401 <<"\", cancelling."<<std::endl;
1405 str_replace(part_of_name, '&', '^');
1406 Strfnd sf(part_of_name);
1408 std::string imagename_top = sf.next("{");
1409 std::string imagename_left = sf.next("{");
1410 std::string imagename_right = sf.next("{");
1412 // Generate images for the faces of the cube
1413 video::IImage *img_top = generateImage(imagename_top);
1414 video::IImage *img_left = generateImage(imagename_left);
1415 video::IImage *img_right = generateImage(imagename_right);
1417 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1418 errorstream << "generateImagePart(): Failed to create textures"
1419 << " for inventorycube \"" << part_of_name << "\""
1421 baseimg = generateImage(imagename_top);
1425 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1427 // Face images are not needed anymore
1435 [lowpart:percent:filename
1436 Adds the lower part of a texture
1438 else if (str_starts_with(part_of_name, "[lowpart:"))
1440 Strfnd sf(part_of_name);
1442 u32 percent = stoi(sf.next(":"));
1443 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1445 if (baseimg == NULL)
1446 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1447 video::IImage *img = generateImage(filename);
1450 core::dimension2d<u32> dim = img->getDimension();
1451 core::position2d<s32> pos_base(0, 0);
1452 video::IImage *img2 =
1453 driver->createImage(video::ECF_A8R8G8B8, dim);
1456 core::position2d<s32> clippos(0, 0);
1457 clippos.Y = dim.Height * (100-percent) / 100;
1458 core::dimension2d<u32> clipdim = dim;
1459 clipdim.Height = clipdim.Height * percent / 100 + 1;
1460 core::rect<s32> cliprect(clippos, clipdim);
1461 img2->copyToWithAlpha(baseimg, pos_base,
1462 core::rect<s32>(v2s32(0,0), dim),
1463 video::SColor(255,255,255,255),
1470 Crops a frame of a vertical animation.
1471 N = frame count, I = frame index
1473 else if (str_starts_with(part_of_name, "[verticalframe:"))
1475 Strfnd sf(part_of_name);
1477 u32 frame_count = stoi(sf.next(":"));
1478 u32 frame_index = stoi(sf.next(":"));
1480 if (baseimg == NULL){
1481 errorstream<<"generateImagePart(): baseimg != NULL "
1482 <<"for part_of_name=\""<<part_of_name
1483 <<"\", cancelling."<<std::endl;
1487 v2u32 frame_size = baseimg->getDimension();
1488 frame_size.Y /= frame_count;
1490 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1493 errorstream<<"generateImagePart(): Could not create image "
1494 <<"for part_of_name=\""<<part_of_name
1495 <<"\", cancelling."<<std::endl;
1499 // Fill target image with transparency
1500 img->fill(video::SColor(0,0,0,0));
1502 core::dimension2d<u32> dim = frame_size;
1503 core::position2d<s32> pos_dst(0, 0);
1504 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1505 baseimg->copyToWithAlpha(img, pos_dst,
1506 core::rect<s32>(pos_src, dim),
1507 video::SColor(255,255,255,255),
1515 Applies a mask to an image
1517 else if (str_starts_with(part_of_name, "[mask:"))
1519 if (baseimg == NULL) {
1520 errorstream << "generateImage(): baseimg == NULL "
1521 << "for part_of_name=\"" << part_of_name
1522 << "\", cancelling." << std::endl;
1525 Strfnd sf(part_of_name);
1527 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1529 video::IImage *img = generateImage(filename);
1531 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1532 img->getDimension());
1535 errorstream << "generateImage(): Failed to load \""
1536 << filename << "\".";
1541 multiplys a given color to any pixel of an image
1542 color = color as ColorString
1544 else if (str_starts_with(part_of_name, "[multiply:")) {
1545 Strfnd sf(part_of_name);
1547 std::string color_str = sf.next(":");
1549 if (baseimg == NULL) {
1550 errorstream << "generateImagePart(): baseimg != NULL "
1551 << "for part_of_name=\"" << part_of_name
1552 << "\", cancelling." << std::endl;
1556 video::SColor color;
1558 if (!parseColorString(color_str, color, false))
1561 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1565 Overlays image with given color
1566 color = color as ColorString
1568 else if (str_starts_with(part_of_name, "[colorize:"))
1570 Strfnd sf(part_of_name);
1572 std::string color_str = sf.next(":");
1573 std::string ratio_str = sf.next(":");
1575 if (baseimg == NULL) {
1576 errorstream << "generateImagePart(): baseimg != NULL "
1577 << "for part_of_name=\"" << part_of_name
1578 << "\", cancelling." << std::endl;
1582 video::SColor color;
1584 bool keep_alpha = false;
1586 if (!parseColorString(color_str, color, false))
1589 if (is_number(ratio_str))
1590 ratio = mystoi(ratio_str, 0, 255);
1591 else if (ratio_str == "alpha")
1594 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1597 [applyfiltersformesh
1600 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1602 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1606 errorstream << "generateImagePart(): baseimg == NULL "
1607 << "for part_of_name=\"" << part_of_name
1608 << "\", cancelling." << std::endl;
1612 // Apply the "clean transparent" filter, if needed
1613 if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent"))
1614 imageCleanTransparent(baseimg, 127);
1616 /* Upscale textures to user's requested minimum size. This is a trick to make
1617 * filters look as good on low-res textures as on high-res ones, by making
1618 * low-res textures BECOME high-res ones. This is helpful for worlds that
1619 * mix high- and low-res textures, or for mods with least-common-denominator
1620 * textures that don't have the resources to offer high-res alternatives.
1622 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1623 const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1;
1625 const core::dimension2d<u32> dim = baseimg->getDimension();
1627 /* Calculate scaling needed to make the shortest texture dimension
1628 * equal to the target minimum. If e.g. this is a vertical frames
1629 * animation, the short dimension will be the real size.
1631 if ((dim.Width == 0) || (dim.Height == 0)) {
1632 errorstream << "generateImagePart(): Illegal 0 dimension "
1633 << "for part_of_name=\""<< part_of_name
1634 << "\", cancelling." << std::endl;
1637 u32 xscale = scaleto / dim.Width;
1638 u32 yscale = scaleto / dim.Height;
1639 u32 scale = (xscale > yscale) ? xscale : yscale;
1641 // Never downscale; only scale up by 2x or more.
1643 u32 w = scale * dim.Width;
1644 u32 h = scale * dim.Height;
1645 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1646 video::IImage *newimg = driver->createImage(
1647 baseimg->getColorFormat(), newdim);
1648 baseimg->copyToScaling(newimg);
1656 Resizes the base image to the given dimensions
1658 else if (str_starts_with(part_of_name, "[resize"))
1660 if (baseimg == NULL) {
1661 errorstream << "generateImagePart(): baseimg == NULL "
1662 << "for part_of_name=\""<< part_of_name
1663 << "\", cancelling." << std::endl;
1667 Strfnd sf(part_of_name);
1669 u32 width = stoi(sf.next("x"));
1670 u32 height = stoi(sf.next(""));
1671 core::dimension2d<u32> dim(width, height);
1673 video::IImage *image = RenderingEngine::get_video_driver()->
1674 createImage(video::ECF_A8R8G8B8, dim);
1675 baseimg->copyToScaling(image);
1681 Makes the base image transparent according to the given ratio.
1682 R must be between 0 and 255.
1683 0 means totally transparent.
1684 255 means totally opaque.
1686 else if (str_starts_with(part_of_name, "[opacity:")) {
1687 if (baseimg == NULL) {
1688 errorstream << "generateImagePart(): baseimg == NULL "
1689 << "for part_of_name=\"" << part_of_name
1690 << "\", cancelling." << std::endl;
1694 Strfnd sf(part_of_name);
1697 u32 ratio = mystoi(sf.next(""), 0, 255);
1699 core::dimension2d<u32> dim = baseimg->getDimension();
1701 for (u32 y = 0; y < dim.Height; y++)
1702 for (u32 x = 0; x < dim.Width; x++)
1704 video::SColor c = baseimg->getPixel(x, y);
1705 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1706 baseimg->setPixel(x, y, c);
1711 Inverts the given channels of the base image.
1712 Mode may contain the characters "r", "g", "b", "a".
1713 Only the channels that are mentioned in the mode string
1716 else if (str_starts_with(part_of_name, "[invert:")) {
1717 if (baseimg == NULL) {
1718 errorstream << "generateImagePart(): baseimg == NULL "
1719 << "for part_of_name=\"" << part_of_name
1720 << "\", cancelling." << std::endl;
1724 Strfnd sf(part_of_name);
1727 std::string mode = sf.next("");
1729 if (mode.find('a') != std::string::npos)
1730 mask |= 0xff000000UL;
1731 if (mode.find('r') != std::string::npos)
1732 mask |= 0x00ff0000UL;
1733 if (mode.find('g') != std::string::npos)
1734 mask |= 0x0000ff00UL;
1735 if (mode.find('b') != std::string::npos)
1736 mask |= 0x000000ffUL;
1738 core::dimension2d<u32> dim = baseimg->getDimension();
1740 for (u32 y = 0; y < dim.Height; y++)
1741 for (u32 x = 0; x < dim.Width; x++)
1743 video::SColor c = baseimg->getPixel(x, y);
1745 baseimg->setPixel(x, y, c);
1750 Retrieves a tile at position X,Y (in tiles)
1751 from the base image it assumes to be a
1752 tilesheet with dimensions W,H (in tiles).
1754 else if (part_of_name.substr(0,7) == "[sheet:") {
1755 if (baseimg == NULL) {
1756 errorstream << "generateImagePart(): baseimg != NULL "
1757 << "for part_of_name=\"" << part_of_name
1758 << "\", cancelling." << std::endl;
1762 Strfnd sf(part_of_name);
1764 u32 w0 = stoi(sf.next("x"));
1765 u32 h0 = stoi(sf.next(":"));
1766 u32 x0 = stoi(sf.next(","));
1767 u32 y0 = stoi(sf.next(":"));
1769 core::dimension2d<u32> img_dim = baseimg->getDimension();
1770 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1772 video::IImage *img = driver->createImage(
1773 video::ECF_A8R8G8B8, tile_dim);
1775 errorstream << "generateImagePart(): Could not create image "
1776 << "for part_of_name=\"" << part_of_name
1777 << "\", cancelling." << std::endl;
1781 img->fill(video::SColor(0,0,0,0));
1782 v2u32 vdim(tile_dim);
1783 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1784 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1785 video::SColor(255,255,255,255), NULL);
1793 Decodes a PNG image in base64 form.
1794 Use minetest.encode_png and minetest.encode_base64
1795 to produce a valid string.
1797 else if (str_starts_with(part_of_name, "[png:")) {
1798 Strfnd sf(part_of_name);
1802 std::string blob = sf.next("");
1803 if (!base64_is_valid(blob)) {
1804 errorstream << "generateImagePart(): "
1805 << "malformed base64 in '[png'"
1809 png = base64_decode(blob);
1812 auto *device = RenderingEngine::get_raw_device();
1813 auto *fs = device->getFileSystem();
1814 auto *vd = device->getVideoDriver();
1815 auto *memfile = fs->createMemoryReadFile(png.data(), png.size(), "__temp_png");
1816 video::IImage* pngimg = vd->createImageFromFile(memfile);
1820 blitBaseImage(pngimg, baseimg);
1822 core::dimension2d<u32> dim = pngimg->getDimension();
1823 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1824 pngimg->copyTo(baseimg);
1830 errorstream << "generateImagePart(): Invalid "
1831 " modification: \"" << part_of_name << "\"" << std::endl;
1839 Calculate the color of a single pixel drawn on top of another pixel.
1841 This is a little more complicated than just video::SColor::getInterpolated
1842 because getInterpolated does not handle alpha correctly. For example, a
1843 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1844 pixel with alpha=160, while getInterpolated would yield alpha=96.
1846 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1848 if (dst_c.getAlpha() == 0)
1850 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1851 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1852 src_c.getAlpha() * ratio / (255 * 255));
1857 Draw an image on top of an another one, using the alpha channel of the
1860 This exists because IImage::copyToWithAlpha() doesn't seem to always
1863 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1864 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1866 for (u32 y0=0; y0<size.Y; y0++)
1867 for (u32 x0=0; x0<size.X; x0++)
1869 s32 src_x = src_pos.X + x0;
1870 s32 src_y = src_pos.Y + y0;
1871 s32 dst_x = dst_pos.X + x0;
1872 s32 dst_y = dst_pos.Y + y0;
1873 video::SColor src_c = src->getPixel(src_x, src_y);
1874 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1875 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1876 dst->setPixel(dst_x, dst_y, dst_c);
1881 Draw an image on top of an another one, using the alpha channel of the
1882 source image; only modify fully opaque pixels in destinaion
1884 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1885 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1887 for (u32 y0=0; y0<size.Y; y0++)
1888 for (u32 x0=0; x0<size.X; x0++)
1890 s32 src_x = src_pos.X + x0;
1891 s32 src_y = src_pos.Y + y0;
1892 s32 dst_x = dst_pos.X + x0;
1893 s32 dst_y = dst_pos.Y + y0;
1894 video::SColor src_c = src->getPixel(src_x, src_y);
1895 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1896 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1898 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1899 dst->setPixel(dst_x, dst_y, dst_c);
1904 // This function has been disabled because it is currently unused.
1905 // Feel free to re-enable if you find it handy.
1908 Draw an image on top of an another one, using the specified ratio
1909 modify all partially-opaque pixels in the destination.
1911 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1912 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1914 for (u32 y0 = 0; y0 < size.Y; y0++)
1915 for (u32 x0 = 0; x0 < size.X; x0++)
1917 s32 src_x = src_pos.X + x0;
1918 s32 src_y = src_pos.Y + y0;
1919 s32 dst_x = dst_pos.X + x0;
1920 s32 dst_y = dst_pos.Y + y0;
1921 video::SColor src_c = src->getPixel(src_x, src_y);
1922 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1923 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1926 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1928 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1929 dst->setPixel(dst_x, dst_y, dst_c);
1936 Apply color to destination
1938 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1939 const video::SColor &color, int ratio, bool keep_alpha)
1941 u32 alpha = color.getAlpha();
1942 video::SColor dst_c;
1943 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1944 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1946 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1947 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1948 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1949 if (dst_alpha > 0) {
1950 dst_c.setAlpha(dst_alpha * alpha / 255);
1951 dst->setPixel(x, y, dst_c);
1954 } else { // replace the color including the alpha
1955 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1956 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1957 if (dst->getPixel(x, y).getAlpha() > 0)
1958 dst->setPixel(x, y, color);
1960 } else { // interpolate between the color and destination
1961 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1962 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1963 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1964 dst_c = dst->getPixel(x, y);
1965 if (dst_c.getAlpha() > 0) {
1966 dst_c = color.getInterpolated(dst_c, interp);
1967 dst->setPixel(x, y, dst_c);
1974 Apply color to destination
1976 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1977 const video::SColor &color)
1979 video::SColor dst_c;
1981 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1982 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1983 dst_c = dst->getPixel(x, y);
1986 (dst_c.getRed() * color.getRed()) / 255,
1987 (dst_c.getGreen() * color.getGreen()) / 255,
1988 (dst_c.getBlue() * color.getBlue()) / 255
1990 dst->setPixel(x, y, dst_c);
1995 Apply mask to destination
1997 static void apply_mask(video::IImage *mask, video::IImage *dst,
1998 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2000 for (u32 y0 = 0; y0 < size.Y; y0++) {
2001 for (u32 x0 = 0; x0 < size.X; x0++) {
2002 s32 mask_x = x0 + mask_pos.X;
2003 s32 mask_y = y0 + mask_pos.Y;
2004 s32 dst_x = x0 + dst_pos.X;
2005 s32 dst_y = y0 + dst_pos.Y;
2006 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2007 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2008 dst_c.color &= mask_c.color;
2009 dst->setPixel(dst_x, dst_y, dst_c);
2014 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2015 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2017 core::dimension2d<u32> strip_size = crack->getDimension();
2018 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2019 core::dimension2d<u32> tile_size(size / tiles);
2020 s32 frame_count = strip_size.Height / strip_size.Width;
2021 if (frame_index >= frame_count)
2022 frame_index = frame_count - 1;
2023 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2024 video::IImage *result = nullptr;
2026 // extract crack frame
2027 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2030 if (tile_size == frame_size) {
2031 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2033 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2035 goto exit__has_tile;
2036 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2037 crack_frame->copyToScaling(crack_tile);
2038 crack_frame->drop();
2044 result = driver->createImage(video::ECF_A8R8G8B8, size);
2046 goto exit__has_tile;
2048 for (u8 i = 0; i < tiles; i++)
2049 for (u8 j = 0; j < tiles; j++)
2050 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2057 static void draw_crack(video::IImage *crack, video::IImage *dst,
2058 bool use_overlay, s32 frame_count, s32 progression,
2059 video::IVideoDriver *driver, u8 tiles)
2061 // Dimension of destination image
2062 core::dimension2d<u32> dim_dst = dst->getDimension();
2063 // Limit frame_count
2064 if (frame_count > (s32) dim_dst.Height)
2065 frame_count = dim_dst.Height;
2066 if (frame_count < 1)
2068 // Dimension of the scaled crack stage,
2069 // which is the same as the dimension of a single destination frame
2070 core::dimension2d<u32> frame_size(
2072 dim_dst.Height / frame_count
2074 video::IImage *crack_scaled = create_crack_image(crack, progression,
2075 frame_size, tiles, driver);
2079 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2080 for (s32 i = 0; i < frame_count; ++i) {
2081 v2s32 dst_pos(0, frame_size.Height * i);
2082 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2085 crack_scaled->drop();
2088 void brighten(video::IImage *image)
2093 core::dimension2d<u32> dim = image->getDimension();
2095 for (u32 y=0; y<dim.Height; y++)
2096 for (u32 x=0; x<dim.Width; x++)
2098 video::SColor c = image->getPixel(x,y);
2099 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2100 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2101 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2102 image->setPixel(x,y,c);
2106 u32 parseImageTransform(const std::string& s)
2108 int total_transform = 0;
2110 std::string transform_names[8];
2111 transform_names[0] = "i";
2112 transform_names[1] = "r90";
2113 transform_names[2] = "r180";
2114 transform_names[3] = "r270";
2115 transform_names[4] = "fx";
2116 transform_names[6] = "fy";
2118 std::size_t pos = 0;
2119 while(pos < s.size())
2122 for (int i = 0; i <= 7; ++i)
2124 const std::string &name_i = transform_names[i];
2126 if (s[pos] == ('0' + i))
2133 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2135 pos += name_i.size();
2142 // Multiply total_transform and transform in the group D4
2145 new_total = (transform + total_transform) % 4;
2147 new_total = (transform - total_transform + 8) % 4;
2148 if ((transform >= 4) ^ (total_transform >= 4))
2151 total_transform = new_total;
2153 return total_transform;
2156 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2158 if (transform % 2 == 0)
2161 return core::dimension2d<u32>(dim.Height, dim.Width);
2164 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2166 if (src == NULL || dst == NULL)
2169 core::dimension2d<u32> dstdim = dst->getDimension();
2172 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2173 assert(transform <= 7);
2176 Compute the transformation from source coordinates (sx,sy)
2177 to destination coordinates (dx,dy).
2181 if (transform == 0) // identity
2182 sxn = 0, syn = 2; // sx = dx, sy = dy
2183 else if (transform == 1) // rotate by 90 degrees ccw
2184 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2185 else if (transform == 2) // rotate by 180 degrees
2186 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2187 else if (transform == 3) // rotate by 270 degrees ccw
2188 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2189 else if (transform == 4) // flip x
2190 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2191 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2192 sxn = 2, syn = 0; // sx = dy, sy = dx
2193 else if (transform == 6) // flip y
2194 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2195 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2196 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2198 for (u32 dy=0; dy<dstdim.Height; dy++)
2199 for (u32 dx=0; dx<dstdim.Width; dx++)
2201 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2202 u32 sx = entries[sxn];
2203 u32 sy = entries[syn];
2204 video::SColor c = src->getPixel(sx,sy);
2205 dst->setPixel(dx,dy,c);
2209 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2211 if (isKnownSourceImage("override_normal.png"))
2212 return getTexture("override_normal.png");
2213 std::string fname_base = name;
2214 static const char *normal_ext = "_normal.png";
2215 static const u32 normal_ext_size = strlen(normal_ext);
2216 size_t pos = fname_base.find('.');
2217 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2218 if (isKnownSourceImage(fname_normal)) {
2219 // look for image extension and replace it
2221 while ((i = fname_base.find('.', i)) != std::string::npos) {
2222 fname_base.replace(i, 4, normal_ext);
2223 i += normal_ext_size;
2225 return getTexture(fname_base);
2231 // For more colourspace transformations, see for example
2232 // https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
2234 inline float linear_to_srgb_component(float v)
2237 return 1.055f * powf(v, 1.0f / 2.4f) - 0.055f;
2240 inline float srgb_to_linear_component(float v)
2243 return powf((v + 0.055f) / 1.055f, 2.4f);
2247 v3f srgb_to_linear(const video::SColor &col_srgb)
2249 v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue());
2251 col.X = srgb_to_linear_component(col.X);
2252 col.Y = srgb_to_linear_component(col.Y);
2253 col.Z = srgb_to_linear_component(col.Z);
2257 video::SColor linear_to_srgb(const v3f &col_linear)
2260 col.X = linear_to_srgb_component(col_linear.X);
2261 col.Y = linear_to_srgb_component(col_linear.Y);
2262 col.Z = linear_to_srgb_component(col_linear.Z);
2264 col.X = core::clamp<float>(col.X, 0.0f, 255.0f);
2265 col.Y = core::clamp<float>(col.Y, 0.0f, 255.0f);
2266 col.Z = core::clamp<float>(col.Z, 0.0f, 255.0f);
2267 return video::SColor(0xff, myround(col.X), myround(col.Y),
2272 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2274 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2275 video::SColor c(0, 0, 0, 0);
2276 video::ITexture *texture = getTexture(name);
2279 video::IImage *image = driver->createImage(texture,
2280 core::position2d<s32>(0, 0),
2281 texture->getOriginalSize());
2286 v3f col_acc(0, 0, 0);
2287 core::dimension2d<u32> dim = image->getDimension();
2290 step = dim.Width / 16;
2291 for (u16 x = 0; x < dim.Width; x += step) {
2292 for (u16 y = 0; y < dim.Width; y += step) {
2293 c = image->getPixel(x,y);
2294 if (c.getAlpha() > 0) {
2296 col_acc += srgb_to_linear(c);
2303 c = linear_to_srgb(col_acc);
2310 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2312 std::string tname = "__shaderFlagsTexture";
2313 tname += normalmap_present ? "1" : "0";
2315 if (isKnownSourceImage(tname)) {
2316 return getTexture(tname);
2319 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2320 video::IImage *flags_image = driver->createImage(
2321 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2322 sanity_check(flags_image != NULL);
2323 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2324 flags_image->setPixel(0, 0, c);
2325 insertSourceImage(tname, flags_image);
2326 flags_image->drop();
2327 return getTexture(tname);
2331 std::vector<std::string> getTextureDirs()
2333 return fs::GetRecursiveDirs(g_settings->get("texture_path"));