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"
39 #ifdef _IRR_COMPILE_WITH_OGLES1_
42 #include <GLES2/gl2.h>
47 A cache from texture name to texture path
49 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
52 Replaces the filename extension.
54 std::string image = "a/image.png"
55 replace_ext(image, "jpg")
56 -> image = "a/image.jpg"
57 Returns true on success.
59 static bool replace_ext(std::string &path, const char *ext)
63 // Find place of last dot, fail if \ or / found.
65 for (s32 i=path.size()-1; i>=0; i--)
73 if (path[i] == '\\' || path[i] == '/')
76 // If not found, return an empty string
79 // Else make the new path
80 path = path.substr(0, last_dot_i+1) + ext;
85 Find out the full path of an image by trying different filename
90 std::string getImagePath(std::string path)
92 // A NULL-ended list of possible image extensions
93 const char *extensions[] = {
94 "png", "jpg", "bmp", "tga",
95 "pcx", "ppm", "psd", "wal", "rgb",
98 // If there is no extension, add one
99 if (removeStringEnd(path, extensions).empty())
100 path = path + ".png";
101 // Check paths until something is found to exist
102 const char **ext = extensions;
104 bool r = replace_ext(path, *ext);
107 if (fs::PathExists(path))
110 while((++ext) != NULL);
116 Gets the path to a texture by first checking if the texture exists
117 in texture_path and if not, using the data path.
119 Checks all supported extensions by replacing the original extension.
121 If not found, returns "".
123 Utilizes a thread-safe cache.
125 std::string getTexturePath(const std::string &filename, bool *is_base_pack)
127 std::string fullpath;
129 // This can set a wrong value on cached textures, but is irrelevant because
130 // is_base_pack is only passed when initializing the textures the first time
132 *is_base_pack = false;
136 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
141 Check from texture_path
143 for (const auto &path : getTextureDirs()) {
144 std::string testpath = path + DIR_DELIM;
145 testpath.append(filename);
146 // Check all filename extensions. Returns "" if not found.
147 fullpath = getImagePath(testpath);
148 if (!fullpath.empty())
153 Check from default data directory
155 if (fullpath.empty())
157 std::string base_path = porting::path_share + DIR_DELIM + "textures"
158 + DIR_DELIM + "base" + DIR_DELIM + "pack";
159 std::string testpath = base_path + DIR_DELIM + filename;
160 // Check all filename extensions. Returns "" if not found.
161 fullpath = getImagePath(testpath);
162 if (is_base_pack && !fullpath.empty())
163 *is_base_pack = true;
166 // Add to cache (also an empty result is cached)
167 g_texturename_to_path_cache.set(filename, fullpath);
173 void clearTextureNameCache()
175 g_texturename_to_path_cache.clear();
179 Stores internal information about a texture.
185 video::ITexture *texture;
188 const std::string &name_,
189 video::ITexture *texture_=NULL
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 // Generate a texture
395 u32 generateTexture(const std::string &name);
397 // Generate image based on a string like "stone.png" or "[crack:1:0".
398 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
399 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
401 /*! Generates an image from a full string like
402 * "stone.png^mineral_coal.png^[crack:1:0".
403 * Shall be called from the main thread.
404 * The returned Image should be dropped.
406 video::IImage* generateImage(const std::string &name);
408 // Thread-safe cache of what source images are known (true = known)
409 MutexedMap<std::string, bool> m_source_image_existence;
411 // A texture id is index in this array.
412 // The first position contains a NULL texture.
413 std::vector<TextureInfo> m_textureinfo_cache;
414 // Maps a texture name to an index in the former.
415 std::map<std::string, u32> m_name_to_id;
416 // The two former containers are behind this mutex
417 std::mutex m_textureinfo_cache_mutex;
419 // Queued texture fetches (to be processed by the main thread)
420 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
422 // Textures that have been overwritten with other ones
423 // but can't be deleted because the ITexture* might still be used
424 std::vector<video::ITexture*> m_texture_trash;
426 // Maps image file names to loaded palettes.
427 std::unordered_map<std::string, Palette> m_palettes;
429 // Cached settings needed for making textures from meshes
430 bool m_setting_trilinear_filter;
431 bool m_setting_bilinear_filter;
432 bool m_setting_anisotropic_filter;
435 IWritableTextureSource *createTextureSource()
437 return new TextureSource();
440 TextureSource::TextureSource()
442 m_main_thread = std::this_thread::get_id();
444 // Add a NULL TextureInfo as the first index, named ""
445 m_textureinfo_cache.emplace_back("");
446 m_name_to_id[""] = 0;
448 // Cache some settings
449 // Note: Since this is only done once, the game must be restarted
450 // for these settings to take effect
451 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
452 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
453 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
456 TextureSource::~TextureSource()
458 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
460 unsigned int textures_before = driver->getTextureCount();
462 for (const auto &iter : m_textureinfo_cache) {
465 driver->removeTexture(iter.texture);
467 m_textureinfo_cache.clear();
469 for (auto t : m_texture_trash) {
470 //cleanup trashed texture
471 driver->removeTexture(t);
474 infostream << "~TextureSource() before cleanup: "<< textures_before
475 << " after: " << driver->getTextureCount() << std::endl;
478 u32 TextureSource::getTextureId(const std::string &name)
480 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
484 See if texture already exists
486 MutexAutoLock lock(m_textureinfo_cache_mutex);
487 std::map<std::string, u32>::iterator n;
488 n = m_name_to_id.find(name);
489 if (n != m_name_to_id.end())
498 if (std::this_thread::get_id() == m_main_thread) {
499 return generateTexture(name);
503 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
505 // We're gonna ask the result to be put into here
506 static ResultQueue<std::string, u32, u8, u8> result_queue;
508 // Throw a request in
509 m_get_texture_queue.add(name, 0, 0, &result_queue);
513 // Wait result for a second
514 GetResult<std::string, u32, u8, u8>
515 result = result_queue.pop_front(1000);
517 if (result.key == name) {
521 } catch(ItemNotFoundException &e) {
522 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
526 infostream << "getTextureId(): Failed" << std::endl;
531 // Draw an image on top of an another one, using the alpha channel of the
533 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
534 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
536 // Like blit_with_alpha, but only modifies destination pixels that
538 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
539 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
541 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
542 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
543 // color alpha with the destination alpha.
544 // Otherwise, any pixels that are not fully transparent get the color alpha.
545 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
546 const video::SColor &color, int ratio, bool keep_alpha);
548 // paint a texture using the given color
549 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
550 const video::SColor &color);
552 // Apply a mask to an image
553 static void apply_mask(video::IImage *mask, video::IImage *dst,
554 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
556 // Draw or overlay a crack
557 static void draw_crack(video::IImage *crack, video::IImage *dst,
558 bool use_overlay, s32 frame_count, s32 progression,
559 video::IVideoDriver *driver, u8 tiles = 1);
562 void brighten(video::IImage *image);
563 // Parse a transform name
564 u32 parseImageTransform(const std::string& s);
565 // Apply transform to image dimension
566 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
567 // Apply transform to image data
568 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
571 This method generates all the textures
573 u32 TextureSource::generateTexture(const std::string &name)
575 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
577 // Empty name means texture 0
579 infostream<<"generateTexture(): name is empty"<<std::endl;
585 See if texture already exists
587 MutexAutoLock lock(m_textureinfo_cache_mutex);
588 std::map<std::string, u32>::iterator n;
589 n = m_name_to_id.find(name);
590 if (n != m_name_to_id.end()) {
596 Calling only allowed from main thread
598 if (std::this_thread::get_id() != m_main_thread) {
599 errorstream<<"TextureSource::generateTexture() "
600 "called not from main thread"<<std::endl;
604 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
605 sanity_check(driver);
607 video::IImage *img = generateImage(name);
609 video::ITexture *tex = NULL;
613 img = Align2Npot2(img, driver);
615 // Create texture from resulting image
616 tex = driver->addTexture(name.c_str(), img);
617 guiScalingCache(io::path(name.c_str()), driver, img);
622 Add texture to caches (add NULL textures too)
625 MutexAutoLock lock(m_textureinfo_cache_mutex);
627 u32 id = m_textureinfo_cache.size();
628 TextureInfo ti(name, tex);
629 m_textureinfo_cache.push_back(ti);
630 m_name_to_id[name] = id;
635 std::string TextureSource::getTextureName(u32 id)
637 MutexAutoLock lock(m_textureinfo_cache_mutex);
639 if (id >= m_textureinfo_cache.size())
641 errorstream<<"TextureSource::getTextureName(): id="<<id
642 <<" >= m_textureinfo_cache.size()="
643 <<m_textureinfo_cache.size()<<std::endl;
647 return m_textureinfo_cache[id].name;
650 video::ITexture* TextureSource::getTexture(u32 id)
652 MutexAutoLock lock(m_textureinfo_cache_mutex);
654 if (id >= m_textureinfo_cache.size())
657 return m_textureinfo_cache[id].texture;
660 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
662 u32 actual_id = getTextureId(name);
666 return getTexture(actual_id);
669 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
671 static thread_local bool filter_needed =
672 g_settings->getBool("texture_clean_transparent") ||
673 ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
674 g_settings->getS32("texture_min_size") > 1);
675 // Avoid duplicating texture if it won't actually change
677 return getTexture(name + "^[applyfiltersformesh", id);
678 return getTexture(name, id);
681 Palette* TextureSource::getPalette(const std::string &name)
683 // Only the main thread may load images
684 sanity_check(std::this_thread::get_id() == m_main_thread);
689 auto it = m_palettes.find(name);
690 if (it == m_palettes.end()) {
692 video::IImage *img = generateImage(name);
694 warningstream << "TextureSource::getPalette(): palette \"" << name
695 << "\" could not be loaded." << std::endl;
699 u32 w = img->getDimension().Width;
700 u32 h = img->getDimension().Height;
701 // Real area of the image
706 warningstream << "TextureSource::getPalette(): the specified"
707 << " palette image \"" << name << "\" is larger than 256"
708 << " pixels, using the first 256." << std::endl;
710 } else if (256 % area != 0)
711 warningstream << "TextureSource::getPalette(): the "
712 << "specified palette image \"" << name << "\" does not "
713 << "contain power of two pixels." << std::endl;
714 // We stretch the palette so it will fit 256 values
715 // This many param2 values will have the same color
716 u32 step = 256 / area;
717 // For each pixel in the image
718 for (u32 i = 0; i < area; i++) {
719 video::SColor c = img->getPixel(i % w, i / w);
720 // Fill in palette with 'step' colors
721 for (u32 j = 0; j < step; j++)
722 new_palette.push_back(c);
725 // Fill in remaining elements
726 while (new_palette.size() < 256)
727 new_palette.emplace_back(0xFFFFFFFF);
728 m_palettes[name] = new_palette;
729 it = m_palettes.find(name);
731 if (it != m_palettes.end())
732 return &((*it).second);
736 void TextureSource::processQueue()
741 //NOTE this is only thread safe for ONE consumer thread!
742 if (!m_get_texture_queue.empty())
744 GetRequest<std::string, u32, u8, u8>
745 request = m_get_texture_queue.pop();
747 /*infostream<<"TextureSource::processQueue(): "
748 <<"got texture request with "
749 <<"name=\""<<request.key<<"\""
752 m_get_texture_queue.pushResult(request, generateTexture(request.key));
756 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
758 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
760 sanity_check(std::this_thread::get_id() == m_main_thread);
762 m_sourcecache.insert(name, img, true);
763 m_source_image_existence.set(name, true);
766 void TextureSource::rebuildImagesAndTextures()
768 MutexAutoLock lock(m_textureinfo_cache_mutex);
770 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
771 sanity_check(driver);
773 infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
774 << " textures" << std::endl;
777 for (TextureInfo &ti : m_textureinfo_cache) {
778 video::IImage *img = generateImage(ti.name);
780 img = Align2Npot2(img, driver);
782 // Create texture from resulting image
783 video::ITexture *t = NULL;
785 t = driver->addTexture(ti.name.c_str(), img);
786 guiScalingCache(io::path(ti.name.c_str()), driver, img);
789 video::ITexture *t_old = ti.texture;
794 m_texture_trash.push_back(t_old);
798 inline static void applyShadeFactor(video::SColor &color, u32 factor)
800 u32 f = core::clamp<u32>(factor, 0, 256);
801 color.setRed(color.getRed() * f / 256);
802 color.setGreen(color.getGreen() * f / 256);
803 color.setBlue(color.getBlue() * f / 256);
806 static video::IImage *createInventoryCubeImage(
807 video::IImage *top, video::IImage *left, video::IImage *right)
809 core::dimension2du size_top = top->getDimension();
810 core::dimension2du size_left = left->getDimension();
811 core::dimension2du size_right = right->getDimension();
813 u32 size = npot2(std::max({
814 size_top.Width, size_top.Height,
815 size_left.Width, size_left.Height,
816 size_right.Width, size_right.Height,
819 // It must be divisible by 4, to let everything work correctly.
820 // But it is a power of 2, so being at least 4 is the same.
821 // And the resulting texture should't be too large as well.
822 size = core::clamp<u32>(size, 4, 64);
824 // With such parameters, the cube fits exactly, touching each image line
825 // from `0` to `cube_size - 1`. (Note that division is exact here).
826 u32 cube_size = 9 * size;
827 u32 offset = size / 2;
829 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
831 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
833 core::dimension2du dim = image->getDimension();
834 video::ECOLOR_FORMAT format = image->getColorFormat();
835 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
836 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
837 image->copyToScaling(scaled);
841 sanity_check(image->getPitch() == 4 * size);
842 return reinterpret_cast<u32 *>(image->lock());
844 auto free_image = [] (video::IImage *image) -> void {
849 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
850 sanity_check(result->getPitch() == 4 * cube_size);
851 result->fill(video::SColor(0x00000000u));
852 u32 *target = reinterpret_cast<u32 *>(result->lock());
854 // Draws single cube face
855 // `shade_factor` is face brightness, in range [0.0, 1.0]
856 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
857 // `offsets` list pixels to be drawn for single source pixel
858 auto draw_image = [=] (video::IImage *image, float shade_factor,
859 s16 xu, s16 xv, s16 x1,
860 s16 yu, s16 yv, s16 y1,
861 std::initializer_list<v2s16> offsets) -> void {
862 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
863 const u32 *source = lock_image(image);
864 for (u16 v = 0; v < size; v++) {
865 for (u16 u = 0; u < size; u++) {
866 video::SColor pixel(*source);
867 applyShadeFactor(pixel, brightness);
868 s16 x = xu * u + xv * v + x1;
869 s16 y = yu * u + yv * v + y1;
870 for (const auto &off : offsets)
871 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
878 draw_image(top, 1.000000f,
879 4, -4, 4 * (size - 1),
882 {2, 0}, {3, 0}, {4, 0}, {5, 0},
883 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
884 {2, 2}, {3, 2}, {4, 2}, {5, 2},
887 draw_image(left, 0.836660f,
892 {0, 1}, {1, 1}, {2, 1}, {3, 1},
893 {0, 2}, {1, 2}, {2, 2}, {3, 2},
894 {0, 3}, {1, 3}, {2, 3}, {3, 3},
895 {0, 4}, {1, 4}, {2, 4}, {3, 4},
899 draw_image(right, 0.670820f,
904 {0, 1}, {1, 1}, {2, 1}, {3, 1},
905 {0, 2}, {1, 2}, {2, 2}, {3, 2},
906 {0, 3}, {1, 3}, {2, 3}, {3, 3},
907 {0, 4}, {1, 4}, {2, 4}, {3, 4},
915 video::IImage* TextureSource::generateImage(const std::string &name)
917 // Get the base image
919 const char separator = '^';
920 const char escape = '\\';
921 const char paren_open = '(';
922 const char paren_close = ')';
924 // Find last separator in the name
925 s32 last_separator_pos = -1;
927 for (s32 i = name.size() - 1; i >= 0; i--) {
928 if (i > 0 && name[i-1] == escape)
932 if (paren_bal == 0) {
933 last_separator_pos = i;
934 i = -1; // break out of loop
938 if (paren_bal == 0) {
939 errorstream << "generateImage(): unbalanced parentheses"
940 << "(extranous '(') while generating texture \""
941 << name << "\"" << std::endl;
954 errorstream << "generateImage(): unbalanced parentheses"
955 << "(missing matching '(') while generating texture \""
956 << name << "\"" << std::endl;
961 video::IImage *baseimg = NULL;
964 If separator was found, make the base image
965 using a recursive call.
967 if (last_separator_pos != -1) {
968 baseimg = generateImage(name.substr(0, last_separator_pos));
972 Parse out the last part of the name of the image and act
976 std::string last_part_of_name = name.substr(last_separator_pos + 1);
979 If this name is enclosed in parentheses, generate it
980 and blit it onto the base image
982 if (last_part_of_name[0] == paren_open
983 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
984 std::string name2 = last_part_of_name.substr(1,
985 last_part_of_name.size() - 2);
986 video::IImage *tmp = generateImage(name2);
988 errorstream << "generateImage(): "
989 "Failed to generate \"" << name2 << "\""
993 core::dimension2d<u32> dim = tmp->getDimension();
995 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1000 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1001 // Generate image according to part of name
1002 errorstream << "generateImage(): "
1003 "Failed to generate \"" << last_part_of_name << "\""
1007 // If no resulting image, print a warning
1008 if (baseimg == NULL) {
1009 errorstream << "generateImage(): baseimg is NULL (attempted to"
1010 " create texture \"" << name << "\")" << std::endl;
1019 static inline u16 get_GL_major_version()
1021 const GLubyte *gl_version = glGetString(GL_VERSION);
1022 return (u16) (gl_version[0] - '0');
1026 * Check if hardware requires npot2 aligned textures
1027 * @return true if alignment NOT(!) requires, false otherwise
1030 bool hasNPotSupport()
1032 // Only GLES2 is trusted to correctly report npot support
1033 // Note: we cache the boolean result, the GL context will never change.
1034 static const bool supported = get_GL_major_version() > 1 &&
1035 glGetString(GL_EXTENSIONS) &&
1036 strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
1041 * Check and align image to npot2 if required by hardware
1042 * @param image image to check for npot2 alignment
1043 * @param driver driver to use for image operations
1044 * @return image or copy of image aligned to npot2
1047 video::IImage * Align2Npot2(video::IImage * image,
1048 video::IVideoDriver* driver)
1053 if (hasNPotSupport())
1056 core::dimension2d<u32> dim = image->getDimension();
1057 unsigned int height = npot2(dim.Height);
1058 unsigned int width = npot2(dim.Width);
1060 if (dim.Height == height && dim.Width == width)
1063 if (dim.Height > height)
1065 if (dim.Width > width)
1068 video::IImage *targetimage =
1069 driver->createImage(video::ECF_A8R8G8B8,
1070 core::dimension2d<u32>(width, height));
1072 if (targetimage != NULL)
1073 image->copyToScaling(targetimage);
1080 static std::string unescape_string(const std::string &str, const char esc = '\\')
1083 size_t pos = 0, cpos;
1084 out.reserve(str.size());
1086 cpos = str.find_first_of(esc, pos);
1087 if (cpos == std::string::npos) {
1088 out += str.substr(pos);
1091 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1097 bool TextureSource::generateImagePart(std::string part_of_name,
1098 video::IImage *& baseimg)
1100 const char escape = '\\'; // same as in generateImage()
1101 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1102 sanity_check(driver);
1104 // Stuff starting with [ are special commands
1105 if (part_of_name.empty() || part_of_name[0] != '[') {
1106 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1108 image = Align2Npot2(image, driver);
1110 if (image == NULL) {
1111 if (!part_of_name.empty()) {
1113 // Do not create normalmap dummies
1114 if (part_of_name.find("_normal.png") != std::string::npos) {
1115 warningstream << "generateImage(): Could not load normal map \""
1116 << part_of_name << "\"" << std::endl;
1120 errorstream << "generateImage(): Could not load image \""
1121 << part_of_name << "\" while building texture; "
1122 "Creating a dummy image" << std::endl;
1125 // Just create a dummy image
1126 //core::dimension2d<u32> dim(2,2);
1127 core::dimension2d<u32> dim(1,1);
1128 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1129 sanity_check(image != NULL);
1130 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1131 image->setPixel(1,0, video::SColor(255,0,255,0));
1132 image->setPixel(0,1, video::SColor(255,0,0,255));
1133 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1134 image->setPixel(0,0, video::SColor(255,myrand()%256,
1135 myrand()%256,myrand()%256));
1136 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1137 myrand()%256,myrand()%256));
1138 image->setPixel(0,1, video::SColor(255,myrand()%256,
1139 myrand()%256,myrand()%256));
1140 image->setPixel(1,1, video::SColor(255,myrand()%256,
1141 myrand()%256,myrand()%256));*/
1144 // If base image is NULL, load as base.
1145 if (baseimg == NULL)
1147 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1149 Copy it this way to get an alpha channel.
1150 Otherwise images with alpha cannot be blitted on
1151 images that don't have alpha in the original file.
1153 core::dimension2d<u32> dim = image->getDimension();
1154 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1155 image->copyTo(baseimg);
1157 // Else blit on base.
1160 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1161 // Size of the copied area
1162 core::dimension2d<u32> dim = image->getDimension();
1163 //core::dimension2d<u32> dim(16,16);
1164 // Position to copy the blitted to in the base image
1165 core::position2d<s32> pos_to(0,0);
1166 // Position to copy the blitted from in the blitted image
1167 core::position2d<s32> pos_from(0,0);
1169 /*image->copyToWithAlpha(baseimg, pos_to,
1170 core::rect<s32>(pos_from, dim),
1171 video::SColor(255,255,255,255),
1174 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1175 if (dim == dim_dst) {
1176 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1177 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1178 // Upscale overlying image
1179 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1180 createImage(video::ECF_A8R8G8B8, dim_dst);
1181 image->copyToScaling(scaled_image);
1183 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1184 scaled_image->drop();
1186 // Upscale base image
1187 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1188 createImage(video::ECF_A8R8G8B8, dim);
1189 baseimg->copyToScaling(scaled_base);
1191 baseimg = scaled_base;
1193 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1201 // A special texture modification
1203 /*infostream<<"generateImage(): generating special "
1204 <<"modification \""<<part_of_name<<"\""
1210 Adds a cracking texture
1211 N = animation frame count, P = crack progression
1213 if (str_starts_with(part_of_name, "[crack"))
1215 if (baseimg == NULL) {
1216 errorstream<<"generateImagePart(): baseimg == NULL "
1217 <<"for part_of_name=\""<<part_of_name
1218 <<"\", cancelling."<<std::endl;
1222 // Crack image number and overlay option
1223 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1224 bool use_overlay = (part_of_name[6] == 'o');
1225 Strfnd sf(part_of_name);
1227 s32 frame_count = stoi(sf.next(":"));
1228 s32 progression = stoi(sf.next(":"));
1230 // Check whether there is the <tiles> argument, that is,
1231 // whether there are 3 arguments. If so, shift values
1232 // as the first and not the last argument is optional.
1233 auto s = sf.next(":");
1235 tiles = frame_count;
1236 frame_count = progression;
1237 progression = stoi(s);
1240 if (progression >= 0) {
1244 It is an image with a number of cracking stages
1247 video::IImage *img_crack = m_sourcecache.getOrLoad(
1248 "crack_anylength.png");
1251 draw_crack(img_crack, baseimg,
1252 use_overlay, frame_count,
1253 progression, driver, tiles);
1259 [combine:WxH:X,Y=filename:X,Y=filename2
1260 Creates a bigger texture from any amount of smaller ones
1262 else if (str_starts_with(part_of_name, "[combine"))
1264 Strfnd sf(part_of_name);
1266 u32 w0 = stoi(sf.next("x"));
1267 u32 h0 = stoi(sf.next(":"));
1268 core::dimension2d<u32> dim(w0,h0);
1269 if (baseimg == NULL) {
1270 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1271 baseimg->fill(video::SColor(0,0,0,0));
1273 while (!sf.at_end()) {
1274 u32 x = stoi(sf.next(","));
1275 u32 y = stoi(sf.next("="));
1276 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1277 infostream<<"Adding \""<<filename
1278 <<"\" to combined ("<<x<<","<<y<<")"
1280 video::IImage *img = generateImage(filename);
1282 core::dimension2d<u32> dim = img->getDimension();
1283 core::position2d<s32> pos_base(x, y);
1284 video::IImage *img2 =
1285 driver->createImage(video::ECF_A8R8G8B8, dim);
1288 /*img2->copyToWithAlpha(baseimg, pos_base,
1289 core::rect<s32>(v2s32(0,0), dim),
1290 video::SColor(255,255,255,255),
1292 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1295 errorstream << "generateImagePart(): Failed to load image \""
1296 << filename << "\" for [combine" << std::endl;
1303 else if (str_starts_with(part_of_name, "[brighten"))
1305 if (baseimg == NULL) {
1306 errorstream<<"generateImagePart(): baseimg==NULL "
1307 <<"for part_of_name=\""<<part_of_name
1308 <<"\", cancelling."<<std::endl;
1316 Make image completely opaque.
1317 Used for the leaves texture when in old leaves mode, so
1318 that the transparent parts don't look completely black
1319 when simple alpha channel is used for rendering.
1321 else if (str_starts_with(part_of_name, "[noalpha"))
1323 if (baseimg == NULL){
1324 errorstream<<"generateImagePart(): baseimg==NULL "
1325 <<"for part_of_name=\""<<part_of_name
1326 <<"\", cancelling."<<std::endl;
1330 core::dimension2d<u32> dim = baseimg->getDimension();
1332 // Set alpha to full
1333 for (u32 y=0; y<dim.Height; y++)
1334 for (u32 x=0; x<dim.Width; x++)
1336 video::SColor c = baseimg->getPixel(x,y);
1338 baseimg->setPixel(x,y,c);
1343 Convert one color to transparent.
1345 else if (str_starts_with(part_of_name, "[makealpha:"))
1347 if (baseimg == NULL) {
1348 errorstream<<"generateImagePart(): baseimg == NULL "
1349 <<"for part_of_name=\""<<part_of_name
1350 <<"\", cancelling."<<std::endl;
1354 Strfnd sf(part_of_name.substr(11));
1355 u32 r1 = stoi(sf.next(","));
1356 u32 g1 = stoi(sf.next(","));
1357 u32 b1 = stoi(sf.next(""));
1359 core::dimension2d<u32> dim = baseimg->getDimension();
1361 /*video::IImage *oldbaseimg = baseimg;
1362 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1363 oldbaseimg->copyTo(baseimg);
1364 oldbaseimg->drop();*/
1366 // Set alpha to full
1367 for (u32 y=0; y<dim.Height; y++)
1368 for (u32 x=0; x<dim.Width; x++)
1370 video::SColor c = baseimg->getPixel(x,y);
1372 u32 g = c.getGreen();
1373 u32 b = c.getBlue();
1374 if (!(r == r1 && g == g1 && b == b1))
1377 baseimg->setPixel(x,y,c);
1382 Rotates and/or flips the image.
1384 N can be a number (between 0 and 7) or a transform name.
1385 Rotations are counter-clockwise.
1387 1 R90 rotate by 90 degrees
1388 2 R180 rotate by 180 degrees
1389 3 R270 rotate by 270 degrees
1391 5 FXR90 flip X then rotate by 90 degrees
1393 7 FYR90 flip Y then rotate by 90 degrees
1395 Note: Transform names can be concatenated to produce
1396 their product (applies the first then the second).
1397 The resulting transform will be equivalent to one of the
1398 eight existing ones, though (see: dihedral group).
1400 else if (str_starts_with(part_of_name, "[transform"))
1402 if (baseimg == NULL) {
1403 errorstream<<"generateImagePart(): baseimg == NULL "
1404 <<"for part_of_name=\""<<part_of_name
1405 <<"\", cancelling."<<std::endl;
1409 u32 transform = parseImageTransform(part_of_name.substr(10));
1410 core::dimension2d<u32> dim = imageTransformDimension(
1411 transform, baseimg->getDimension());
1412 video::IImage *image = driver->createImage(
1413 baseimg->getColorFormat(), dim);
1414 sanity_check(image != NULL);
1415 imageTransform(transform, baseimg, image);
1420 [inventorycube{topimage{leftimage{rightimage
1421 In every subimage, replace ^ with &.
1422 Create an "inventory cube".
1423 NOTE: This should be used only on its own.
1424 Example (a grass block (not actually used in game):
1425 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1427 else if (str_starts_with(part_of_name, "[inventorycube"))
1429 if (baseimg != NULL){
1430 errorstream<<"generateImagePart(): baseimg != NULL "
1431 <<"for part_of_name=\""<<part_of_name
1432 <<"\", cancelling."<<std::endl;
1436 str_replace(part_of_name, '&', '^');
1437 Strfnd sf(part_of_name);
1439 std::string imagename_top = sf.next("{");
1440 std::string imagename_left = sf.next("{");
1441 std::string imagename_right = sf.next("{");
1443 // Generate images for the faces of the cube
1444 video::IImage *img_top = generateImage(imagename_top);
1445 video::IImage *img_left = generateImage(imagename_left);
1446 video::IImage *img_right = generateImage(imagename_right);
1448 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1449 errorstream << "generateImagePart(): Failed to create textures"
1450 << " for inventorycube \"" << part_of_name << "\""
1452 baseimg = generateImage(imagename_top);
1456 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1458 // Face images are not needed anymore
1466 [lowpart:percent:filename
1467 Adds the lower part of a texture
1469 else if (str_starts_with(part_of_name, "[lowpart:"))
1471 Strfnd sf(part_of_name);
1473 u32 percent = stoi(sf.next(":"));
1474 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1476 if (baseimg == NULL)
1477 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1478 video::IImage *img = generateImage(filename);
1481 core::dimension2d<u32> dim = img->getDimension();
1482 core::position2d<s32> pos_base(0, 0);
1483 video::IImage *img2 =
1484 driver->createImage(video::ECF_A8R8G8B8, dim);
1487 core::position2d<s32> clippos(0, 0);
1488 clippos.Y = dim.Height * (100-percent) / 100;
1489 core::dimension2d<u32> clipdim = dim;
1490 clipdim.Height = clipdim.Height * percent / 100 + 1;
1491 core::rect<s32> cliprect(clippos, clipdim);
1492 img2->copyToWithAlpha(baseimg, pos_base,
1493 core::rect<s32>(v2s32(0,0), dim),
1494 video::SColor(255,255,255,255),
1501 Crops a frame of a vertical animation.
1502 N = frame count, I = frame index
1504 else if (str_starts_with(part_of_name, "[verticalframe:"))
1506 Strfnd sf(part_of_name);
1508 u32 frame_count = stoi(sf.next(":"));
1509 u32 frame_index = stoi(sf.next(":"));
1511 if (baseimg == NULL){
1512 errorstream<<"generateImagePart(): baseimg != NULL "
1513 <<"for part_of_name=\""<<part_of_name
1514 <<"\", cancelling."<<std::endl;
1518 v2u32 frame_size = baseimg->getDimension();
1519 frame_size.Y /= frame_count;
1521 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1524 errorstream<<"generateImagePart(): Could not create image "
1525 <<"for part_of_name=\""<<part_of_name
1526 <<"\", cancelling."<<std::endl;
1530 // Fill target image with transparency
1531 img->fill(video::SColor(0,0,0,0));
1533 core::dimension2d<u32> dim = frame_size;
1534 core::position2d<s32> pos_dst(0, 0);
1535 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1536 baseimg->copyToWithAlpha(img, pos_dst,
1537 core::rect<s32>(pos_src, dim),
1538 video::SColor(255,255,255,255),
1546 Applies a mask to an image
1548 else if (str_starts_with(part_of_name, "[mask:"))
1550 if (baseimg == NULL) {
1551 errorstream << "generateImage(): baseimg == NULL "
1552 << "for part_of_name=\"" << part_of_name
1553 << "\", cancelling." << std::endl;
1556 Strfnd sf(part_of_name);
1558 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1560 video::IImage *img = generateImage(filename);
1562 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1563 img->getDimension());
1566 errorstream << "generateImage(): Failed to load \""
1567 << filename << "\".";
1572 multiplys a given color to any pixel of an image
1573 color = color as ColorString
1575 else if (str_starts_with(part_of_name, "[multiply:")) {
1576 Strfnd sf(part_of_name);
1578 std::string color_str = sf.next(":");
1580 if (baseimg == NULL) {
1581 errorstream << "generateImagePart(): baseimg != NULL "
1582 << "for part_of_name=\"" << part_of_name
1583 << "\", cancelling." << std::endl;
1587 video::SColor color;
1589 if (!parseColorString(color_str, color, false))
1592 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1596 Overlays image with given color
1597 color = color as ColorString
1599 else if (str_starts_with(part_of_name, "[colorize:"))
1601 Strfnd sf(part_of_name);
1603 std::string color_str = sf.next(":");
1604 std::string ratio_str = sf.next(":");
1606 if (baseimg == NULL) {
1607 errorstream << "generateImagePart(): baseimg != NULL "
1608 << "for part_of_name=\"" << part_of_name
1609 << "\", cancelling." << std::endl;
1613 video::SColor color;
1615 bool keep_alpha = false;
1617 if (!parseColorString(color_str, color, false))
1620 if (is_number(ratio_str))
1621 ratio = mystoi(ratio_str, 0, 255);
1622 else if (ratio_str == "alpha")
1625 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1628 [applyfiltersformesh
1631 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1633 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1637 errorstream << "generateImagePart(): baseimg == NULL "
1638 << "for part_of_name=\"" << part_of_name
1639 << "\", cancelling." << std::endl;
1643 // Apply the "clean transparent" filter, if configured.
1644 if (g_settings->getBool("texture_clean_transparent"))
1645 imageCleanTransparent(baseimg, 127);
1647 /* Upscale textures to user's requested minimum size. This is a trick to make
1648 * filters look as good on low-res textures as on high-res ones, by making
1649 * low-res textures BECOME high-res ones. This is helpful for worlds that
1650 * mix high- and low-res textures, or for mods with least-common-denominator
1651 * textures that don't have the resources to offer high-res alternatives.
1653 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1654 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1656 const core::dimension2d<u32> dim = baseimg->getDimension();
1658 /* Calculate scaling needed to make the shortest texture dimension
1659 * equal to the target minimum. If e.g. this is a vertical frames
1660 * animation, the short dimension will be the real size.
1662 if ((dim.Width == 0) || (dim.Height == 0)) {
1663 errorstream << "generateImagePart(): Illegal 0 dimension "
1664 << "for part_of_name=\""<< part_of_name
1665 << "\", cancelling." << std::endl;
1668 u32 xscale = scaleto / dim.Width;
1669 u32 yscale = scaleto / dim.Height;
1670 u32 scale = (xscale > yscale) ? xscale : yscale;
1672 // Never downscale; only scale up by 2x or more.
1674 u32 w = scale * dim.Width;
1675 u32 h = scale * dim.Height;
1676 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1677 video::IImage *newimg = driver->createImage(
1678 baseimg->getColorFormat(), newdim);
1679 baseimg->copyToScaling(newimg);
1687 Resizes the base image to the given dimensions
1689 else if (str_starts_with(part_of_name, "[resize"))
1691 if (baseimg == NULL) {
1692 errorstream << "generateImagePart(): baseimg == NULL "
1693 << "for part_of_name=\""<< part_of_name
1694 << "\", cancelling." << std::endl;
1698 Strfnd sf(part_of_name);
1700 u32 width = stoi(sf.next("x"));
1701 u32 height = stoi(sf.next(""));
1702 core::dimension2d<u32> dim(width, height);
1704 video::IImage *image = RenderingEngine::get_video_driver()->
1705 createImage(video::ECF_A8R8G8B8, dim);
1706 baseimg->copyToScaling(image);
1712 Makes the base image transparent according to the given ratio.
1713 R must be between 0 and 255.
1714 0 means totally transparent.
1715 255 means totally opaque.
1717 else if (str_starts_with(part_of_name, "[opacity:")) {
1718 if (baseimg == NULL) {
1719 errorstream << "generateImagePart(): baseimg == NULL "
1720 << "for part_of_name=\"" << part_of_name
1721 << "\", cancelling." << std::endl;
1725 Strfnd sf(part_of_name);
1728 u32 ratio = mystoi(sf.next(""), 0, 255);
1730 core::dimension2d<u32> dim = baseimg->getDimension();
1732 for (u32 y = 0; y < dim.Height; y++)
1733 for (u32 x = 0; x < dim.Width; x++)
1735 video::SColor c = baseimg->getPixel(x, y);
1736 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1737 baseimg->setPixel(x, y, c);
1742 Inverts the given channels of the base image.
1743 Mode may contain the characters "r", "g", "b", "a".
1744 Only the channels that are mentioned in the mode string
1747 else if (str_starts_with(part_of_name, "[invert:")) {
1748 if (baseimg == NULL) {
1749 errorstream << "generateImagePart(): baseimg == NULL "
1750 << "for part_of_name=\"" << part_of_name
1751 << "\", cancelling." << std::endl;
1755 Strfnd sf(part_of_name);
1758 std::string mode = sf.next("");
1760 if (mode.find('a') != std::string::npos)
1761 mask |= 0xff000000UL;
1762 if (mode.find('r') != std::string::npos)
1763 mask |= 0x00ff0000UL;
1764 if (mode.find('g') != std::string::npos)
1765 mask |= 0x0000ff00UL;
1766 if (mode.find('b') != std::string::npos)
1767 mask |= 0x000000ffUL;
1769 core::dimension2d<u32> dim = baseimg->getDimension();
1771 for (u32 y = 0; y < dim.Height; y++)
1772 for (u32 x = 0; x < dim.Width; x++)
1774 video::SColor c = baseimg->getPixel(x, y);
1776 baseimg->setPixel(x, y, c);
1781 Retrieves a tile at position X,Y (in tiles)
1782 from the base image it assumes to be a
1783 tilesheet with dimensions W,H (in tiles).
1785 else if (part_of_name.substr(0,7) == "[sheet:") {
1786 if (baseimg == NULL) {
1787 errorstream << "generateImagePart(): baseimg != NULL "
1788 << "for part_of_name=\"" << part_of_name
1789 << "\", cancelling." << std::endl;
1793 Strfnd sf(part_of_name);
1795 u32 w0 = stoi(sf.next("x"));
1796 u32 h0 = stoi(sf.next(":"));
1797 u32 x0 = stoi(sf.next(","));
1798 u32 y0 = stoi(sf.next(":"));
1800 core::dimension2d<u32> img_dim = baseimg->getDimension();
1801 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1803 video::IImage *img = driver->createImage(
1804 video::ECF_A8R8G8B8, tile_dim);
1806 errorstream << "generateImagePart(): Could not create image "
1807 << "for part_of_name=\"" << part_of_name
1808 << "\", cancelling." << std::endl;
1812 img->fill(video::SColor(0,0,0,0));
1813 v2u32 vdim(tile_dim);
1814 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1815 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1816 video::SColor(255,255,255,255), NULL);
1824 errorstream << "generateImagePart(): Invalid "
1825 " modification: \"" << part_of_name << "\"" << std::endl;
1833 Calculate the color of a single pixel drawn on top of another pixel.
1835 This is a little more complicated than just video::SColor::getInterpolated
1836 because getInterpolated does not handle alpha correctly. For example, a
1837 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1838 pixel with alpha=160, while getInterpolated would yield alpha=96.
1840 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1842 if (dst_c.getAlpha() == 0)
1844 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1845 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1846 src_c.getAlpha() * ratio / (255 * 255));
1851 Draw an image on top of an another one, using the alpha channel of the
1854 This exists because IImage::copyToWithAlpha() doesn't seem to always
1857 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1858 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1860 for (u32 y0=0; y0<size.Y; y0++)
1861 for (u32 x0=0; x0<size.X; x0++)
1863 s32 src_x = src_pos.X + x0;
1864 s32 src_y = src_pos.Y + y0;
1865 s32 dst_x = dst_pos.X + x0;
1866 s32 dst_y = dst_pos.Y + y0;
1867 video::SColor src_c = src->getPixel(src_x, src_y);
1868 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1869 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1870 dst->setPixel(dst_x, dst_y, dst_c);
1875 Draw an image on top of an another one, using the alpha channel of the
1876 source image; only modify fully opaque pixels in destinaion
1878 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1879 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1881 for (u32 y0=0; y0<size.Y; y0++)
1882 for (u32 x0=0; x0<size.X; x0++)
1884 s32 src_x = src_pos.X + x0;
1885 s32 src_y = src_pos.Y + y0;
1886 s32 dst_x = dst_pos.X + x0;
1887 s32 dst_y = dst_pos.Y + y0;
1888 video::SColor src_c = src->getPixel(src_x, src_y);
1889 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1890 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1892 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1893 dst->setPixel(dst_x, dst_y, dst_c);
1898 // This function has been disabled because it is currently unused.
1899 // Feel free to re-enable if you find it handy.
1902 Draw an image on top of an another one, using the specified ratio
1903 modify all partially-opaque pixels in the destination.
1905 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1906 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1908 for (u32 y0 = 0; y0 < size.Y; y0++)
1909 for (u32 x0 = 0; x0 < size.X; x0++)
1911 s32 src_x = src_pos.X + x0;
1912 s32 src_y = src_pos.Y + y0;
1913 s32 dst_x = dst_pos.X + x0;
1914 s32 dst_y = dst_pos.Y + y0;
1915 video::SColor src_c = src->getPixel(src_x, src_y);
1916 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1917 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1920 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1922 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1923 dst->setPixel(dst_x, dst_y, dst_c);
1930 Apply color to destination
1932 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1933 const video::SColor &color, int ratio, bool keep_alpha)
1935 u32 alpha = color.getAlpha();
1936 video::SColor dst_c;
1937 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1938 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1940 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1941 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1942 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1943 if (dst_alpha > 0) {
1944 dst_c.setAlpha(dst_alpha * alpha / 255);
1945 dst->setPixel(x, y, dst_c);
1948 } else { // replace the color including the alpha
1949 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1950 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1951 if (dst->getPixel(x, y).getAlpha() > 0)
1952 dst->setPixel(x, y, color);
1954 } else { // interpolate between the color and destination
1955 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1956 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1957 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1958 dst_c = dst->getPixel(x, y);
1959 if (dst_c.getAlpha() > 0) {
1960 dst_c = color.getInterpolated(dst_c, interp);
1961 dst->setPixel(x, y, dst_c);
1968 Apply color to destination
1970 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1971 const video::SColor &color)
1973 video::SColor dst_c;
1975 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1976 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1977 dst_c = dst->getPixel(x, y);
1980 (dst_c.getRed() * color.getRed()) / 255,
1981 (dst_c.getGreen() * color.getGreen()) / 255,
1982 (dst_c.getBlue() * color.getBlue()) / 255
1984 dst->setPixel(x, y, dst_c);
1989 Apply mask to destination
1991 static void apply_mask(video::IImage *mask, video::IImage *dst,
1992 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1994 for (u32 y0 = 0; y0 < size.Y; y0++) {
1995 for (u32 x0 = 0; x0 < size.X; x0++) {
1996 s32 mask_x = x0 + mask_pos.X;
1997 s32 mask_y = y0 + mask_pos.Y;
1998 s32 dst_x = x0 + dst_pos.X;
1999 s32 dst_y = y0 + dst_pos.Y;
2000 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2001 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2002 dst_c.color &= mask_c.color;
2003 dst->setPixel(dst_x, dst_y, dst_c);
2008 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2009 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2011 core::dimension2d<u32> strip_size = crack->getDimension();
2012 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2013 core::dimension2d<u32> tile_size(size / tiles);
2014 s32 frame_count = strip_size.Height / strip_size.Width;
2015 if (frame_index >= frame_count)
2016 frame_index = frame_count - 1;
2017 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2018 video::IImage *result = nullptr;
2020 // extract crack frame
2021 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2024 if (tile_size == frame_size) {
2025 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2027 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2029 goto exit__has_tile;
2030 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2031 crack_frame->copyToScaling(crack_tile);
2032 crack_frame->drop();
2038 result = driver->createImage(video::ECF_A8R8G8B8, size);
2040 goto exit__has_tile;
2042 for (u8 i = 0; i < tiles; i++)
2043 for (u8 j = 0; j < tiles; j++)
2044 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2051 static void draw_crack(video::IImage *crack, video::IImage *dst,
2052 bool use_overlay, s32 frame_count, s32 progression,
2053 video::IVideoDriver *driver, u8 tiles)
2055 // Dimension of destination image
2056 core::dimension2d<u32> dim_dst = dst->getDimension();
2057 // Limit frame_count
2058 if (frame_count > (s32) dim_dst.Height)
2059 frame_count = dim_dst.Height;
2060 if (frame_count < 1)
2062 // Dimension of the scaled crack stage,
2063 // which is the same as the dimension of a single destination frame
2064 core::dimension2d<u32> frame_size(
2066 dim_dst.Height / frame_count
2068 video::IImage *crack_scaled = create_crack_image(crack, progression,
2069 frame_size, tiles, driver);
2073 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2074 for (s32 i = 0; i < frame_count; ++i) {
2075 v2s32 dst_pos(0, frame_size.Height * i);
2076 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2079 crack_scaled->drop();
2082 void brighten(video::IImage *image)
2087 core::dimension2d<u32> dim = image->getDimension();
2089 for (u32 y=0; y<dim.Height; y++)
2090 for (u32 x=0; x<dim.Width; x++)
2092 video::SColor c = image->getPixel(x,y);
2093 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2094 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2095 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2096 image->setPixel(x,y,c);
2100 u32 parseImageTransform(const std::string& s)
2102 int total_transform = 0;
2104 std::string transform_names[8];
2105 transform_names[0] = "i";
2106 transform_names[1] = "r90";
2107 transform_names[2] = "r180";
2108 transform_names[3] = "r270";
2109 transform_names[4] = "fx";
2110 transform_names[6] = "fy";
2112 std::size_t pos = 0;
2113 while(pos < s.size())
2116 for (int i = 0; i <= 7; ++i)
2118 const std::string &name_i = transform_names[i];
2120 if (s[pos] == ('0' + i))
2127 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2129 pos += name_i.size();
2136 // Multiply total_transform and transform in the group D4
2139 new_total = (transform + total_transform) % 4;
2141 new_total = (transform - total_transform + 8) % 4;
2142 if ((transform >= 4) ^ (total_transform >= 4))
2145 total_transform = new_total;
2147 return total_transform;
2150 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2152 if (transform % 2 == 0)
2155 return core::dimension2d<u32>(dim.Height, dim.Width);
2158 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2160 if (src == NULL || dst == NULL)
2163 core::dimension2d<u32> dstdim = dst->getDimension();
2166 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2167 assert(transform <= 7);
2170 Compute the transformation from source coordinates (sx,sy)
2171 to destination coordinates (dx,dy).
2175 if (transform == 0) // identity
2176 sxn = 0, syn = 2; // sx = dx, sy = dy
2177 else if (transform == 1) // rotate by 90 degrees ccw
2178 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2179 else if (transform == 2) // rotate by 180 degrees
2180 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2181 else if (transform == 3) // rotate by 270 degrees ccw
2182 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2183 else if (transform == 4) // flip x
2184 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2185 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2186 sxn = 2, syn = 0; // sx = dy, sy = dx
2187 else if (transform == 6) // flip y
2188 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2189 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2190 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2192 for (u32 dy=0; dy<dstdim.Height; dy++)
2193 for (u32 dx=0; dx<dstdim.Width; dx++)
2195 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2196 u32 sx = entries[sxn];
2197 u32 sy = entries[syn];
2198 video::SColor c = src->getPixel(sx,sy);
2199 dst->setPixel(dx,dy,c);
2203 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2205 if (isKnownSourceImage("override_normal.png"))
2206 return getTexture("override_normal.png");
2207 std::string fname_base = name;
2208 static const char *normal_ext = "_normal.png";
2209 static const u32 normal_ext_size = strlen(normal_ext);
2210 size_t pos = fname_base.find('.');
2211 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2212 if (isKnownSourceImage(fname_normal)) {
2213 // look for image extension and replace it
2215 while ((i = fname_base.find('.', i)) != std::string::npos) {
2216 fname_base.replace(i, 4, normal_ext);
2217 i += normal_ext_size;
2219 return getTexture(fname_base);
2224 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2226 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2227 video::SColor c(0, 0, 0, 0);
2228 video::ITexture *texture = getTexture(name);
2229 video::IImage *image = driver->createImage(texture,
2230 core::position2d<s32>(0, 0),
2231 texture->getOriginalSize());
2236 core::dimension2d<u32> dim = image->getDimension();
2239 step = dim.Width / 16;
2240 for (u16 x = 0; x < dim.Width; x += step) {
2241 for (u16 y = 0; y < dim.Width; y += step) {
2242 c = image->getPixel(x,y);
2243 if (c.getAlpha() > 0) {
2253 c.setRed(tR / total);
2254 c.setGreen(tG / total);
2255 c.setBlue(tB / total);
2262 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2264 std::string tname = "__shaderFlagsTexture";
2265 tname += normalmap_present ? "1" : "0";
2267 if (isKnownSourceImage(tname)) {
2268 return getTexture(tname);
2271 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2272 video::IImage *flags_image = driver->createImage(
2273 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2274 sanity_check(flags_image != NULL);
2275 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2276 flags_image->setPixel(0, 0, c);
2277 insertSourceImage(tname, flags_image);
2278 flags_image->drop();
2279 return getTexture(tname);
2283 std::vector<std::string> getTextureDirs()
2285 return fs::GetRecursiveDirs(g_settings->get("texture_path"));