3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <ICameraSceneNode.h>
24 #include "util/string.h"
25 #include "util/container.h"
26 #include "util/thread.h"
31 #include "util/strfnd.h"
32 #include "imagefilters.h"
33 #include "guiscalingfilter.h"
34 #include "renderingengine.h"
42 A cache from texture name to texture path
44 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
47 Replaces the filename extension.
49 std::string image = "a/image.png"
50 replace_ext(image, "jpg")
51 -> image = "a/image.jpg"
52 Returns true on success.
54 static bool replace_ext(std::string &path, const char *ext)
58 // Find place of last dot, fail if \ or / found.
60 for (s32 i=path.size()-1; i>=0; i--)
68 if (path[i] == '\\' || path[i] == '/')
71 // If not found, return an empty string
74 // Else make the new path
75 path = path.substr(0, last_dot_i+1) + ext;
80 Find out the full path of an image by trying different filename
85 std::string getImagePath(std::string path)
87 // A NULL-ended list of possible image extensions
88 const char *extensions[] = {
89 "png", "jpg", "bmp", "tga",
90 "pcx", "ppm", "psd", "wal", "rgb",
93 // If there is no extension, add one
94 if (removeStringEnd(path, extensions).empty())
96 // Check paths until something is found to exist
97 const char **ext = extensions;
99 bool r = replace_ext(path, *ext);
102 if (fs::PathExists(path))
105 while((++ext) != NULL);
111 Gets the path to a texture by first checking if the texture exists
112 in texture_path and if not, using the data path.
114 Checks all supported extensions by replacing the original extension.
116 If not found, returns "".
118 Utilizes a thread-safe cache.
120 std::string getTexturePath(const std::string &filename)
122 std::string fullpath;
126 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
131 Check from texture_path
133 for (const auto &path : getTextureDirs()) {
134 std::string testpath = path + DIR_DELIM + filename;
135 // Check all filename extensions. Returns "" if not found.
136 fullpath = getImagePath(testpath);
137 if (!fullpath.empty())
142 Check from default data directory
144 if (fullpath.empty())
146 std::string base_path = porting::path_share + DIR_DELIM + "textures"
147 + DIR_DELIM + "base" + DIR_DELIM + "pack";
148 std::string testpath = base_path + DIR_DELIM + filename;
149 // Check all filename extensions. Returns "" if not found.
150 fullpath = getImagePath(testpath);
153 // Add to cache (also an empty result is cached)
154 g_texturename_to_path_cache.set(filename, fullpath);
160 void clearTextureNameCache()
162 g_texturename_to_path_cache.clear();
166 Stores internal information about a texture.
172 video::ITexture *texture;
175 const std::string &name_,
176 video::ITexture *texture_=NULL
185 SourceImageCache: A cache used for storing source images.
188 class SourceImageCache
191 ~SourceImageCache() {
192 for (auto &m_image : m_images) {
193 m_image.second->drop();
197 void insert(const std::string &name, video::IImage *img, bool prefer_local)
199 assert(img); // Pre-condition
201 std::map<std::string, video::IImage*>::iterator n;
202 n = m_images.find(name);
203 if (n != m_images.end()){
208 video::IImage* toadd = img;
209 bool need_to_grab = true;
211 // Try to use local texture instead if asked to
213 std::string path = getTexturePath(name);
215 video::IImage *img2 = RenderingEngine::get_video_driver()->
216 createImageFromFile(path.c_str());
219 need_to_grab = false;
226 m_images[name] = toadd;
228 video::IImage* get(const std::string &name)
230 std::map<std::string, video::IImage*>::iterator n;
231 n = m_images.find(name);
232 if (n != m_images.end())
236 // Primarily fetches from cache, secondarily tries to read from filesystem
237 video::IImage *getOrLoad(const std::string &name)
239 std::map<std::string, video::IImage*>::iterator n;
240 n = m_images.find(name);
241 if (n != m_images.end()){
242 n->second->grab(); // Grab for caller
245 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
246 std::string path = getTexturePath(name);
248 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
249 <<name<<"\""<<std::endl;
252 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
254 video::IImage *img = driver->createImageFromFile(path.c_str());
257 m_images[name] = img;
258 img->grab(); // Grab for caller
263 std::map<std::string, video::IImage*> m_images;
270 class TextureSource : public IWritableTextureSource
274 virtual ~TextureSource();
278 Now, assume a texture with the id 1 exists, and has the name
279 "stone.png^mineral1".
280 Then a random thread calls getTextureId for a texture called
281 "stone.png^mineral1^crack0".
282 ...Now, WTF should happen? Well:
283 - getTextureId strips off stuff recursively from the end until
284 the remaining part is found, or nothing is left when
285 something is stripped out
287 But it is slow to search for textures by names and modify them
289 - ContentFeatures is made to contain ids for the basic plain
291 - Crack textures can be slow by themselves, but the framework
295 - Assume a texture with the id 1 exists, and has the name
296 "stone.png^mineral_coal.png".
297 - Now getNodeTile() stumbles upon a node which uses
298 texture id 1, and determines that MATERIAL_FLAG_CRACK
299 must be applied to the tile
300 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
301 has received the current crack level 0 from the client. It
302 finds out the name of the texture with getTextureName(1),
303 appends "^crack0" to it and gets a new texture id with
304 getTextureId("stone.png^mineral_coal.png^crack0").
309 Gets a texture id from cache or
310 - if main thread, generates the texture, adds to cache and returns id.
311 - if other thread, adds to request queue and waits for main thread.
313 The id 0 points to a NULL texture. It is returned in case of error.
315 u32 getTextureId(const std::string &name);
317 // Finds out the name of a cached texture.
318 std::string getTextureName(u32 id);
321 If texture specified by the name pointed by the id doesn't
322 exist, create it, then return the cached texture.
324 Can be called from any thread. If called from some other thread
325 and not found in cache, the call is queued to the main thread
328 video::ITexture* getTexture(u32 id);
330 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
333 Get a texture specifically intended for mesh
334 application, i.e. not HUD, compositing, or other 2D
335 use. This texture may be a different size and may
336 have had additional filters applied.
338 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
340 virtual Palette* getPalette(const std::string &name);
342 bool isKnownSourceImage(const std::string &name)
344 bool is_known = false;
345 bool cache_found = m_source_image_existence.get(name, &is_known);
348 // Not found in cache; find out if a local file exists
349 is_known = (!getTexturePath(name).empty());
350 m_source_image_existence.set(name, is_known);
354 // Processes queued texture requests from other threads.
355 // Shall be called from the main thread.
358 // Insert an image into the cache without touching the filesystem.
359 // Shall be called from the main thread.
360 void insertSourceImage(const std::string &name, video::IImage *img);
362 // Rebuild images and textures from the current set of source images
363 // Shall be called from the main thread.
364 void rebuildImagesAndTextures();
366 video::ITexture* getNormalTexture(const std::string &name);
367 video::SColor getTextureAverageColor(const std::string &name);
368 video::ITexture *getShaderFlagsTexture(bool normamap_present);
372 // The id of the thread that is allowed to use irrlicht directly
373 std::thread::id m_main_thread;
375 // Cache of source images
376 // This should be only accessed from the main thread
377 SourceImageCache m_sourcecache;
379 // Generate a texture
380 u32 generateTexture(const std::string &name);
382 // Generate image based on a string like "stone.png" or "[crack:1:0".
383 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
384 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
386 /*! Generates an image from a full string like
387 * "stone.png^mineral_coal.png^[crack:1:0".
388 * Shall be called from the main thread.
389 * The returned Image should be dropped.
391 video::IImage* generateImage(const std::string &name);
393 // Thread-safe cache of what source images are known (true = known)
394 MutexedMap<std::string, bool> m_source_image_existence;
396 // A texture id is index in this array.
397 // The first position contains a NULL texture.
398 std::vector<TextureInfo> m_textureinfo_cache;
399 // Maps a texture name to an index in the former.
400 std::map<std::string, u32> m_name_to_id;
401 // The two former containers are behind this mutex
402 std::mutex m_textureinfo_cache_mutex;
404 // Queued texture fetches (to be processed by the main thread)
405 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
407 // Textures that have been overwritten with other ones
408 // but can't be deleted because the ITexture* might still be used
409 std::vector<video::ITexture*> m_texture_trash;
411 // Maps image file names to loaded palettes.
412 std::unordered_map<std::string, Palette> m_palettes;
414 // Cached settings needed for making textures from meshes
415 bool m_setting_trilinear_filter;
416 bool m_setting_bilinear_filter;
417 bool m_setting_anisotropic_filter;
420 IWritableTextureSource *createTextureSource()
422 return new TextureSource();
425 TextureSource::TextureSource()
427 m_main_thread = std::this_thread::get_id();
429 // Add a NULL TextureInfo as the first index, named ""
430 m_textureinfo_cache.emplace_back("");
431 m_name_to_id[""] = 0;
433 // Cache some settings
434 // Note: Since this is only done once, the game must be restarted
435 // for these settings to take effect
436 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
437 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
438 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
441 TextureSource::~TextureSource()
443 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
445 unsigned int textures_before = driver->getTextureCount();
447 for (const auto &iter : m_textureinfo_cache) {
450 driver->removeTexture(iter.texture);
452 m_textureinfo_cache.clear();
454 for (auto t : m_texture_trash) {
455 //cleanup trashed texture
456 driver->removeTexture(t);
459 infostream << "~TextureSource() "<< textures_before << "/"
460 << driver->getTextureCount() << std::endl;
463 u32 TextureSource::getTextureId(const std::string &name)
465 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
469 See if texture already exists
471 MutexAutoLock lock(m_textureinfo_cache_mutex);
472 std::map<std::string, u32>::iterator n;
473 n = m_name_to_id.find(name);
474 if (n != m_name_to_id.end())
483 if (std::this_thread::get_id() == m_main_thread) {
484 return generateTexture(name);
488 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
490 // We're gonna ask the result to be put into here
491 static ResultQueue<std::string, u32, u8, u8> result_queue;
493 // Throw a request in
494 m_get_texture_queue.add(name, 0, 0, &result_queue);
498 // Wait result for a second
499 GetResult<std::string, u32, u8, u8>
500 result = result_queue.pop_front(1000);
502 if (result.key == name) {
506 } catch(ItemNotFoundException &e) {
507 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
511 infostream << "getTextureId(): Failed" << std::endl;
516 // Draw an image on top of an another one, using the alpha channel of the
518 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
519 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
521 // Like blit_with_alpha, but only modifies destination pixels that
523 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
524 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
526 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
527 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
528 // color alpha with the destination alpha.
529 // Otherwise, any pixels that are not fully transparent get the color alpha.
530 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
531 const video::SColor &color, int ratio, bool keep_alpha);
533 // paint a texture using the given color
534 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
535 const video::SColor &color);
537 // Apply a mask to an image
538 static void apply_mask(video::IImage *mask, video::IImage *dst,
539 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
541 // Draw or overlay a crack
542 static void draw_crack(video::IImage *crack, video::IImage *dst,
543 bool use_overlay, s32 frame_count, s32 progression,
544 video::IVideoDriver *driver, u8 tiles = 1);
547 void brighten(video::IImage *image);
548 // Parse a transform name
549 u32 parseImageTransform(const std::string& s);
550 // Apply transform to image dimension
551 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
552 // Apply transform to image data
553 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
556 This method generates all the textures
558 u32 TextureSource::generateTexture(const std::string &name)
560 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
562 // Empty name means texture 0
564 infostream<<"generateTexture(): name is empty"<<std::endl;
570 See if texture already exists
572 MutexAutoLock lock(m_textureinfo_cache_mutex);
573 std::map<std::string, u32>::iterator n;
574 n = m_name_to_id.find(name);
575 if (n != m_name_to_id.end()) {
581 Calling only allowed from main thread
583 if (std::this_thread::get_id() != m_main_thread) {
584 errorstream<<"TextureSource::generateTexture() "
585 "called not from main thread"<<std::endl;
589 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
590 sanity_check(driver);
592 video::IImage *img = generateImage(name);
594 video::ITexture *tex = NULL;
598 img = Align2Npot2(img, driver);
600 // Create texture from resulting image
601 tex = driver->addTexture(name.c_str(), img);
602 guiScalingCache(io::path(name.c_str()), driver, img);
607 Add texture to caches (add NULL textures too)
610 MutexAutoLock lock(m_textureinfo_cache_mutex);
612 u32 id = m_textureinfo_cache.size();
613 TextureInfo ti(name, tex);
614 m_textureinfo_cache.push_back(ti);
615 m_name_to_id[name] = id;
620 std::string TextureSource::getTextureName(u32 id)
622 MutexAutoLock lock(m_textureinfo_cache_mutex);
624 if (id >= m_textureinfo_cache.size())
626 errorstream<<"TextureSource::getTextureName(): id="<<id
627 <<" >= m_textureinfo_cache.size()="
628 <<m_textureinfo_cache.size()<<std::endl;
632 return m_textureinfo_cache[id].name;
635 video::ITexture* TextureSource::getTexture(u32 id)
637 MutexAutoLock lock(m_textureinfo_cache_mutex);
639 if (id >= m_textureinfo_cache.size())
642 return m_textureinfo_cache[id].texture;
645 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
647 u32 actual_id = getTextureId(name);
651 return getTexture(actual_id);
654 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
656 return getTexture(name + "^[applyfiltersformesh", id);
659 Palette* TextureSource::getPalette(const std::string &name)
661 // Only the main thread may load images
662 sanity_check(std::this_thread::get_id() == m_main_thread);
667 auto it = m_palettes.find(name);
668 if (it == m_palettes.end()) {
670 video::IImage *img = generateImage(name);
672 warningstream << "TextureSource::getPalette(): palette \"" << name
673 << "\" could not be loaded." << std::endl;
677 u32 w = img->getDimension().Width;
678 u32 h = img->getDimension().Height;
679 // Real area of the image
684 warningstream << "TextureSource::getPalette(): the specified"
685 << " palette image \"" << name << "\" is larger than 256"
686 << " pixels, using the first 256." << std::endl;
688 } else if (256 % area != 0)
689 warningstream << "TextureSource::getPalette(): the "
690 << "specified palette image \"" << name << "\" does not "
691 << "contain power of two pixels." << std::endl;
692 // We stretch the palette so it will fit 256 values
693 // This many param2 values will have the same color
694 u32 step = 256 / area;
695 // For each pixel in the image
696 for (u32 i = 0; i < area; i++) {
697 video::SColor c = img->getPixel(i % w, i / w);
698 // Fill in palette with 'step' colors
699 for (u32 j = 0; j < step; j++)
700 new_palette.push_back(c);
703 // Fill in remaining elements
704 while (new_palette.size() < 256)
705 new_palette.emplace_back(0xFFFFFFFF);
706 m_palettes[name] = new_palette;
707 it = m_palettes.find(name);
709 if (it != m_palettes.end())
710 return &((*it).second);
714 void TextureSource::processQueue()
719 //NOTE this is only thread safe for ONE consumer thread!
720 if (!m_get_texture_queue.empty())
722 GetRequest<std::string, u32, u8, u8>
723 request = m_get_texture_queue.pop();
725 /*infostream<<"TextureSource::processQueue(): "
726 <<"got texture request with "
727 <<"name=\""<<request.key<<"\""
730 m_get_texture_queue.pushResult(request, generateTexture(request.key));
734 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
736 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
738 sanity_check(std::this_thread::get_id() == m_main_thread);
740 m_sourcecache.insert(name, img, true);
741 m_source_image_existence.set(name, true);
744 void TextureSource::rebuildImagesAndTextures()
746 MutexAutoLock lock(m_textureinfo_cache_mutex);
748 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
749 sanity_check(driver);
752 for (TextureInfo &ti : m_textureinfo_cache) {
753 video::IImage *img = generateImage(ti.name);
755 img = Align2Npot2(img, driver);
757 // Create texture from resulting image
758 video::ITexture *t = NULL;
760 t = driver->addTexture(ti.name.c_str(), img);
761 guiScalingCache(io::path(ti.name.c_str()), driver, img);
764 video::ITexture *t_old = ti.texture;
769 m_texture_trash.push_back(t_old);
773 inline static void applyShadeFactor(video::SColor &color, u32 factor)
775 u32 f = core::clamp<u32>(factor, 0, 256);
776 color.setRed(color.getRed() * f / 256);
777 color.setGreen(color.getGreen() * f / 256);
778 color.setBlue(color.getBlue() * f / 256);
781 static video::IImage *createInventoryCubeImage(
782 video::IImage *top, video::IImage *left, video::IImage *right)
784 core::dimension2du size_top = top->getDimension();
785 core::dimension2du size_left = left->getDimension();
786 core::dimension2du size_right = right->getDimension();
788 u32 size = npot2(std::max({
789 size_top.Width, size_top.Height,
790 size_left.Width, size_left.Height,
791 size_right.Width, size_right.Height,
794 // It must be divisible by 4, to let everything work correctly.
795 // But it is a power of 2, so being at least 4 is the same.
796 // And the resulting texture should't be too large as well.
797 size = core::clamp<u32>(size, 4, 64);
799 // With such parameters, the cube fits exactly, touching each image line
800 // from `0` to `cube_size - 1`. (Note that division is exact here).
801 u32 cube_size = 9 * size;
802 u32 offset = size / 2;
804 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
806 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
808 core::dimension2du dim = image->getDimension();
809 video::ECOLOR_FORMAT format = image->getColorFormat();
810 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
811 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
812 image->copyToScaling(scaled);
816 sanity_check(image->getPitch() == 4 * size);
817 return reinterpret_cast<u32 *>(image->lock());
819 auto free_image = [] (video::IImage *image) -> void {
824 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
825 sanity_check(result->getPitch() == 4 * cube_size);
826 result->fill(video::SColor(0x00000000u));
827 u32 *target = reinterpret_cast<u32 *>(result->lock());
829 // Draws single cube face
830 // `shade_factor` is face brightness, in range [0.0, 1.0]
831 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
832 // `offsets` list pixels to be drawn for single source pixel
833 auto draw_image = [=] (video::IImage *image, float shade_factor,
834 s16 xu, s16 xv, s16 x1,
835 s16 yu, s16 yv, s16 y1,
836 std::initializer_list<v2s16> offsets) -> void {
837 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
838 const u32 *source = lock_image(image);
839 for (u16 v = 0; v < size; v++) {
840 for (u16 u = 0; u < size; u++) {
841 video::SColor pixel(*source);
842 applyShadeFactor(pixel, brightness);
843 s16 x = xu * u + xv * v + x1;
844 s16 y = yu * u + yv * v + y1;
845 for (const auto &off : offsets)
846 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
853 draw_image(top, 1.000000f,
854 4, -4, 4 * (size - 1),
857 {2, 0}, {3, 0}, {4, 0}, {5, 0},
858 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
859 {2, 2}, {3, 2}, {4, 2}, {5, 2},
862 draw_image(left, 0.836660f,
867 {0, 1}, {1, 1}, {2, 1}, {3, 1},
868 {0, 2}, {1, 2}, {2, 2}, {3, 2},
869 {0, 3}, {1, 3}, {2, 3}, {3, 3},
870 {0, 4}, {1, 4}, {2, 4}, {3, 4},
874 draw_image(right, 0.670820f,
879 {0, 1}, {1, 1}, {2, 1}, {3, 1},
880 {0, 2}, {1, 2}, {2, 2}, {3, 2},
881 {0, 3}, {1, 3}, {2, 3}, {3, 3},
882 {0, 4}, {1, 4}, {2, 4}, {3, 4},
890 video::IImage* TextureSource::generateImage(const std::string &name)
892 // Get the base image
894 const char separator = '^';
895 const char escape = '\\';
896 const char paren_open = '(';
897 const char paren_close = ')';
899 // Find last separator in the name
900 s32 last_separator_pos = -1;
902 for (s32 i = name.size() - 1; i >= 0; i--) {
903 if (i > 0 && name[i-1] == escape)
907 if (paren_bal == 0) {
908 last_separator_pos = i;
909 i = -1; // break out of loop
913 if (paren_bal == 0) {
914 errorstream << "generateImage(): unbalanced parentheses"
915 << "(extranous '(') while generating texture \""
916 << name << "\"" << std::endl;
929 errorstream << "generateImage(): unbalanced parentheses"
930 << "(missing matching '(') while generating texture \""
931 << name << "\"" << std::endl;
936 video::IImage *baseimg = NULL;
939 If separator was found, make the base image
940 using a recursive call.
942 if (last_separator_pos != -1) {
943 baseimg = generateImage(name.substr(0, last_separator_pos));
947 Parse out the last part of the name of the image and act
951 std::string last_part_of_name = name.substr(last_separator_pos + 1);
954 If this name is enclosed in parentheses, generate it
955 and blit it onto the base image
957 if (last_part_of_name[0] == paren_open
958 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
959 std::string name2 = last_part_of_name.substr(1,
960 last_part_of_name.size() - 2);
961 video::IImage *tmp = generateImage(name2);
963 errorstream << "generateImage(): "
964 "Failed to generate \"" << name2 << "\""
968 core::dimension2d<u32> dim = tmp->getDimension();
970 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
975 } else if (!generateImagePart(last_part_of_name, baseimg)) {
976 // Generate image according to part of name
977 errorstream << "generateImage(): "
978 "Failed to generate \"" << last_part_of_name << "\""
982 // If no resulting image, print a warning
983 if (baseimg == NULL) {
984 errorstream << "generateImage(): baseimg is NULL (attempted to"
985 " create texture \"" << name << "\")" << std::endl;
994 * Check and align image to npot2 if required by hardware
995 * @param image image to check for npot2 alignment
996 * @param driver driver to use for image operations
997 * @return image or copy of image aligned to npot2
1000 inline u16 get_GL_major_version()
1002 const GLubyte *gl_version = glGetString(GL_VERSION);
1003 return (u16) (gl_version[0] - '0');
1006 video::IImage * Align2Npot2(video::IImage * image,
1007 video::IVideoDriver* driver)
1009 if (image == NULL) {
1013 core::dimension2d<u32> dim = image->getDimension();
1015 // Only GLES2 is trusted to correctly report npot support
1016 // Note: we cache the boolean result. GL context will never change on Android.
1017 static const bool hasNPotSupport = get_GL_major_version() > 1 &&
1018 glGetString(GL_EXTENSIONS) &&
1019 strstr(glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
1024 unsigned int height = npot2(dim.Height);
1025 unsigned int width = npot2(dim.Width);
1027 if ((dim.Height == height) &&
1028 (dim.Width == width)) {
1032 if (dim.Height > height) {
1036 if (dim.Width > width) {
1040 video::IImage *targetimage =
1041 driver->createImage(video::ECF_A8R8G8B8,
1042 core::dimension2d<u32>(width, height));
1044 if (targetimage != NULL) {
1045 image->copyToScaling(targetimage);
1053 static std::string unescape_string(const std::string &str, const char esc = '\\')
1056 size_t pos = 0, cpos;
1057 out.reserve(str.size());
1059 cpos = str.find_first_of(esc, pos);
1060 if (cpos == std::string::npos) {
1061 out += str.substr(pos);
1064 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1070 bool TextureSource::generateImagePart(std::string part_of_name,
1071 video::IImage *& baseimg)
1073 const char escape = '\\'; // same as in generateImage()
1074 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1075 sanity_check(driver);
1077 // Stuff starting with [ are special commands
1078 if (part_of_name.empty() || part_of_name[0] != '[') {
1079 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1081 image = Align2Npot2(image, driver);
1083 if (image == NULL) {
1084 if (!part_of_name.empty()) {
1086 // Do not create normalmap dummies
1087 if (part_of_name.find("_normal.png") != std::string::npos) {
1088 warningstream << "generateImage(): Could not load normal map \""
1089 << part_of_name << "\"" << std::endl;
1093 errorstream << "generateImage(): Could not load image \""
1094 << part_of_name << "\" while building texture; "
1095 "Creating a dummy image" << std::endl;
1098 // Just create a dummy image
1099 //core::dimension2d<u32> dim(2,2);
1100 core::dimension2d<u32> dim(1,1);
1101 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1102 sanity_check(image != NULL);
1103 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1104 image->setPixel(1,0, video::SColor(255,0,255,0));
1105 image->setPixel(0,1, video::SColor(255,0,0,255));
1106 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1107 image->setPixel(0,0, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));
1109 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));
1111 image->setPixel(0,1, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));
1113 image->setPixel(1,1, video::SColor(255,myrand()%256,
1114 myrand()%256,myrand()%256));*/
1117 // If base image is NULL, load as base.
1118 if (baseimg == NULL)
1120 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1122 Copy it this way to get an alpha channel.
1123 Otherwise images with alpha cannot be blitted on
1124 images that don't have alpha in the original file.
1126 core::dimension2d<u32> dim = image->getDimension();
1127 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1128 image->copyTo(baseimg);
1130 // Else blit on base.
1133 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1134 // Size of the copied area
1135 core::dimension2d<u32> dim = image->getDimension();
1136 //core::dimension2d<u32> dim(16,16);
1137 // Position to copy the blitted to in the base image
1138 core::position2d<s32> pos_to(0,0);
1139 // Position to copy the blitted from in the blitted image
1140 core::position2d<s32> pos_from(0,0);
1142 /*image->copyToWithAlpha(baseimg, pos_to,
1143 core::rect<s32>(pos_from, dim),
1144 video::SColor(255,255,255,255),
1147 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1148 if (dim == dim_dst) {
1149 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1150 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1151 // Upscale overlying image
1152 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1153 createImage(video::ECF_A8R8G8B8, dim_dst);
1154 image->copyToScaling(scaled_image);
1156 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1157 scaled_image->drop();
1159 // Upscale base image
1160 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1161 createImage(video::ECF_A8R8G8B8, dim);
1162 baseimg->copyToScaling(scaled_base);
1164 baseimg = scaled_base;
1166 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1174 // A special texture modification
1176 /*infostream<<"generateImage(): generating special "
1177 <<"modification \""<<part_of_name<<"\""
1183 Adds a cracking texture
1184 N = animation frame count, P = crack progression
1186 if (str_starts_with(part_of_name, "[crack"))
1188 if (baseimg == NULL) {
1189 errorstream<<"generateImagePart(): baseimg == NULL "
1190 <<"for part_of_name=\""<<part_of_name
1191 <<"\", cancelling."<<std::endl;
1195 // Crack image number and overlay option
1196 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1197 bool use_overlay = (part_of_name[6] == 'o');
1198 Strfnd sf(part_of_name);
1200 s32 frame_count = stoi(sf.next(":"));
1201 s32 progression = stoi(sf.next(":"));
1203 // Check whether there is the <tiles> argument, that is,
1204 // whether there are 3 arguments. If so, shift values
1205 // as the first and not the last argument is optional.
1206 auto s = sf.next(":");
1208 tiles = frame_count;
1209 frame_count = progression;
1210 progression = stoi(s);
1213 if (progression >= 0) {
1217 It is an image with a number of cracking stages
1220 video::IImage *img_crack = m_sourcecache.getOrLoad(
1221 "crack_anylength.png");
1224 draw_crack(img_crack, baseimg,
1225 use_overlay, frame_count,
1226 progression, driver, tiles);
1232 [combine:WxH:X,Y=filename:X,Y=filename2
1233 Creates a bigger texture from any amount of smaller ones
1235 else if (str_starts_with(part_of_name, "[combine"))
1237 Strfnd sf(part_of_name);
1239 u32 w0 = stoi(sf.next("x"));
1240 u32 h0 = stoi(sf.next(":"));
1241 core::dimension2d<u32> dim(w0,h0);
1242 if (baseimg == NULL) {
1243 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1244 baseimg->fill(video::SColor(0,0,0,0));
1246 while (!sf.at_end()) {
1247 u32 x = stoi(sf.next(","));
1248 u32 y = stoi(sf.next("="));
1249 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1250 infostream<<"Adding \""<<filename
1251 <<"\" to combined ("<<x<<","<<y<<")"
1253 video::IImage *img = generateImage(filename);
1255 core::dimension2d<u32> dim = img->getDimension();
1256 infostream<<"Size "<<dim.Width
1257 <<"x"<<dim.Height<<std::endl;
1258 core::position2d<s32> pos_base(x, y);
1259 video::IImage *img2 =
1260 driver->createImage(video::ECF_A8R8G8B8, dim);
1263 /*img2->copyToWithAlpha(baseimg, pos_base,
1264 core::rect<s32>(v2s32(0,0), dim),
1265 video::SColor(255,255,255,255),
1267 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1270 errorstream << "generateImagePart(): Failed to load image \""
1271 << filename << "\" for [combine" << std::endl;
1278 else if (str_starts_with(part_of_name, "[brighten"))
1280 if (baseimg == NULL) {
1281 errorstream<<"generateImagePart(): baseimg==NULL "
1282 <<"for part_of_name=\""<<part_of_name
1283 <<"\", cancelling."<<std::endl;
1291 Make image completely opaque.
1292 Used for the leaves texture when in old leaves mode, so
1293 that the transparent parts don't look completely black
1294 when simple alpha channel is used for rendering.
1296 else if (str_starts_with(part_of_name, "[noalpha"))
1298 if (baseimg == NULL){
1299 errorstream<<"generateImagePart(): baseimg==NULL "
1300 <<"for part_of_name=\""<<part_of_name
1301 <<"\", cancelling."<<std::endl;
1305 core::dimension2d<u32> dim = baseimg->getDimension();
1307 // Set alpha to full
1308 for (u32 y=0; y<dim.Height; y++)
1309 for (u32 x=0; x<dim.Width; x++)
1311 video::SColor c = baseimg->getPixel(x,y);
1313 baseimg->setPixel(x,y,c);
1318 Convert one color to transparent.
1320 else if (str_starts_with(part_of_name, "[makealpha:"))
1322 if (baseimg == NULL) {
1323 errorstream<<"generateImagePart(): baseimg == NULL "
1324 <<"for part_of_name=\""<<part_of_name
1325 <<"\", cancelling."<<std::endl;
1329 Strfnd sf(part_of_name.substr(11));
1330 u32 r1 = stoi(sf.next(","));
1331 u32 g1 = stoi(sf.next(","));
1332 u32 b1 = stoi(sf.next(""));
1334 core::dimension2d<u32> dim = baseimg->getDimension();
1336 /*video::IImage *oldbaseimg = baseimg;
1337 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1338 oldbaseimg->copyTo(baseimg);
1339 oldbaseimg->drop();*/
1341 // Set alpha to full
1342 for (u32 y=0; y<dim.Height; y++)
1343 for (u32 x=0; x<dim.Width; x++)
1345 video::SColor c = baseimg->getPixel(x,y);
1347 u32 g = c.getGreen();
1348 u32 b = c.getBlue();
1349 if (!(r == r1 && g == g1 && b == b1))
1352 baseimg->setPixel(x,y,c);
1357 Rotates and/or flips the image.
1359 N can be a number (between 0 and 7) or a transform name.
1360 Rotations are counter-clockwise.
1362 1 R90 rotate by 90 degrees
1363 2 R180 rotate by 180 degrees
1364 3 R270 rotate by 270 degrees
1366 5 FXR90 flip X then rotate by 90 degrees
1368 7 FYR90 flip Y then rotate by 90 degrees
1370 Note: Transform names can be concatenated to produce
1371 their product (applies the first then the second).
1372 The resulting transform will be equivalent to one of the
1373 eight existing ones, though (see: dihedral group).
1375 else if (str_starts_with(part_of_name, "[transform"))
1377 if (baseimg == NULL) {
1378 errorstream<<"generateImagePart(): baseimg == NULL "
1379 <<"for part_of_name=\""<<part_of_name
1380 <<"\", cancelling."<<std::endl;
1384 u32 transform = parseImageTransform(part_of_name.substr(10));
1385 core::dimension2d<u32> dim = imageTransformDimension(
1386 transform, baseimg->getDimension());
1387 video::IImage *image = driver->createImage(
1388 baseimg->getColorFormat(), dim);
1389 sanity_check(image != NULL);
1390 imageTransform(transform, baseimg, image);
1395 [inventorycube{topimage{leftimage{rightimage
1396 In every subimage, replace ^ with &.
1397 Create an "inventory cube".
1398 NOTE: This should be used only on its own.
1399 Example (a grass block (not actually used in game):
1400 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1402 else if (str_starts_with(part_of_name, "[inventorycube"))
1404 if (baseimg != NULL){
1405 errorstream<<"generateImagePart(): baseimg != NULL "
1406 <<"for part_of_name=\""<<part_of_name
1407 <<"\", cancelling."<<std::endl;
1411 str_replace(part_of_name, '&', '^');
1412 Strfnd sf(part_of_name);
1414 std::string imagename_top = sf.next("{");
1415 std::string imagename_left = sf.next("{");
1416 std::string imagename_right = sf.next("{");
1418 // Generate images for the faces of the cube
1419 video::IImage *img_top = generateImage(imagename_top);
1420 video::IImage *img_left = generateImage(imagename_left);
1421 video::IImage *img_right = generateImage(imagename_right);
1423 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1424 errorstream << "generateImagePart(): Failed to create textures"
1425 << " for inventorycube \"" << part_of_name << "\""
1427 baseimg = generateImage(imagename_top);
1431 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1433 // Face images are not needed anymore
1441 [lowpart:percent:filename
1442 Adds the lower part of a texture
1444 else if (str_starts_with(part_of_name, "[lowpart:"))
1446 Strfnd sf(part_of_name);
1448 u32 percent = stoi(sf.next(":"));
1449 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1451 if (baseimg == NULL)
1452 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1453 video::IImage *img = generateImage(filename);
1456 core::dimension2d<u32> dim = img->getDimension();
1457 core::position2d<s32> pos_base(0, 0);
1458 video::IImage *img2 =
1459 driver->createImage(video::ECF_A8R8G8B8, dim);
1462 core::position2d<s32> clippos(0, 0);
1463 clippos.Y = dim.Height * (100-percent) / 100;
1464 core::dimension2d<u32> clipdim = dim;
1465 clipdim.Height = clipdim.Height * percent / 100 + 1;
1466 core::rect<s32> cliprect(clippos, clipdim);
1467 img2->copyToWithAlpha(baseimg, pos_base,
1468 core::rect<s32>(v2s32(0,0), dim),
1469 video::SColor(255,255,255,255),
1476 Crops a frame of a vertical animation.
1477 N = frame count, I = frame index
1479 else if (str_starts_with(part_of_name, "[verticalframe:"))
1481 Strfnd sf(part_of_name);
1483 u32 frame_count = stoi(sf.next(":"));
1484 u32 frame_index = stoi(sf.next(":"));
1486 if (baseimg == NULL){
1487 errorstream<<"generateImagePart(): baseimg != NULL "
1488 <<"for part_of_name=\""<<part_of_name
1489 <<"\", cancelling."<<std::endl;
1493 v2u32 frame_size = baseimg->getDimension();
1494 frame_size.Y /= frame_count;
1496 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1499 errorstream<<"generateImagePart(): Could not create image "
1500 <<"for part_of_name=\""<<part_of_name
1501 <<"\", cancelling."<<std::endl;
1505 // Fill target image with transparency
1506 img->fill(video::SColor(0,0,0,0));
1508 core::dimension2d<u32> dim = frame_size;
1509 core::position2d<s32> pos_dst(0, 0);
1510 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1511 baseimg->copyToWithAlpha(img, pos_dst,
1512 core::rect<s32>(pos_src, dim),
1513 video::SColor(255,255,255,255),
1521 Applies a mask to an image
1523 else if (str_starts_with(part_of_name, "[mask:"))
1525 if (baseimg == NULL) {
1526 errorstream << "generateImage(): baseimg == NULL "
1527 << "for part_of_name=\"" << part_of_name
1528 << "\", cancelling." << std::endl;
1531 Strfnd sf(part_of_name);
1533 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1535 video::IImage *img = generateImage(filename);
1537 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1538 img->getDimension());
1541 errorstream << "generateImage(): Failed to load \""
1542 << filename << "\".";
1547 multiplys a given color to any pixel of an image
1548 color = color as ColorString
1550 else if (str_starts_with(part_of_name, "[multiply:")) {
1551 Strfnd sf(part_of_name);
1553 std::string color_str = sf.next(":");
1555 if (baseimg == NULL) {
1556 errorstream << "generateImagePart(): baseimg != NULL "
1557 << "for part_of_name=\"" << part_of_name
1558 << "\", cancelling." << std::endl;
1562 video::SColor color;
1564 if (!parseColorString(color_str, color, false))
1567 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1571 Overlays image with given color
1572 color = color as ColorString
1574 else if (str_starts_with(part_of_name, "[colorize:"))
1576 Strfnd sf(part_of_name);
1578 std::string color_str = sf.next(":");
1579 std::string ratio_str = sf.next(":");
1581 if (baseimg == NULL) {
1582 errorstream << "generateImagePart(): baseimg != NULL "
1583 << "for part_of_name=\"" << part_of_name
1584 << "\", cancelling." << std::endl;
1588 video::SColor color;
1590 bool keep_alpha = false;
1592 if (!parseColorString(color_str, color, false))
1595 if (is_number(ratio_str))
1596 ratio = mystoi(ratio_str, 0, 255);
1597 else if (ratio_str == "alpha")
1600 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1603 [applyfiltersformesh
1606 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1608 // Apply the "clean transparent" filter, if configured.
1609 if (g_settings->getBool("texture_clean_transparent"))
1610 imageCleanTransparent(baseimg, 127);
1612 /* Upscale textures to user's requested minimum size. This is a trick to make
1613 * filters look as good on low-res textures as on high-res ones, by making
1614 * low-res textures BECOME high-res ones. This is helpful for worlds that
1615 * mix high- and low-res textures, or for mods with least-common-denominator
1616 * textures that don't have the resources to offer high-res alternatives.
1618 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1619 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1621 const core::dimension2d<u32> dim = baseimg->getDimension();
1623 /* Calculate scaling needed to make the shortest texture dimension
1624 * equal to the target minimum. If e.g. this is a vertical frames
1625 * animation, the short dimension will be the real size.
1627 if ((dim.Width == 0) || (dim.Height == 0)) {
1628 errorstream << "generateImagePart(): Illegal 0 dimension "
1629 << "for part_of_name=\""<< part_of_name
1630 << "\", cancelling." << std::endl;
1633 u32 xscale = scaleto / dim.Width;
1634 u32 yscale = scaleto / dim.Height;
1635 u32 scale = (xscale > yscale) ? xscale : yscale;
1637 // Never downscale; only scale up by 2x or more.
1639 u32 w = scale * dim.Width;
1640 u32 h = scale * dim.Height;
1641 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1642 video::IImage *newimg = driver->createImage(
1643 baseimg->getColorFormat(), newdim);
1644 baseimg->copyToScaling(newimg);
1652 Resizes the base image to the given dimensions
1654 else if (str_starts_with(part_of_name, "[resize"))
1656 if (baseimg == NULL) {
1657 errorstream << "generateImagePart(): baseimg == NULL "
1658 << "for part_of_name=\""<< part_of_name
1659 << "\", cancelling." << std::endl;
1663 Strfnd sf(part_of_name);
1665 u32 width = stoi(sf.next("x"));
1666 u32 height = stoi(sf.next(""));
1667 core::dimension2d<u32> dim(width, height);
1669 video::IImage *image = RenderingEngine::get_video_driver()->
1670 createImage(video::ECF_A8R8G8B8, dim);
1671 baseimg->copyToScaling(image);
1677 Makes the base image transparent according to the given ratio.
1678 R must be between 0 and 255.
1679 0 means totally transparent.
1680 255 means totally opaque.
1682 else if (str_starts_with(part_of_name, "[opacity:")) {
1683 if (baseimg == NULL) {
1684 errorstream << "generateImagePart(): baseimg == NULL "
1685 << "for part_of_name=\"" << part_of_name
1686 << "\", cancelling." << std::endl;
1690 Strfnd sf(part_of_name);
1693 u32 ratio = mystoi(sf.next(""), 0, 255);
1695 core::dimension2d<u32> dim = baseimg->getDimension();
1697 for (u32 y = 0; y < dim.Height; y++)
1698 for (u32 x = 0; x < dim.Width; x++)
1700 video::SColor c = baseimg->getPixel(x, y);
1701 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1702 baseimg->setPixel(x, y, c);
1707 Inverts the given channels of the base image.
1708 Mode may contain the characters "r", "g", "b", "a".
1709 Only the channels that are mentioned in the mode string
1712 else if (str_starts_with(part_of_name, "[invert:")) {
1713 if (baseimg == NULL) {
1714 errorstream << "generateImagePart(): baseimg == NULL "
1715 << "for part_of_name=\"" << part_of_name
1716 << "\", cancelling." << std::endl;
1720 Strfnd sf(part_of_name);
1723 std::string mode = sf.next("");
1725 if (mode.find('a') != std::string::npos)
1726 mask |= 0xff000000UL;
1727 if (mode.find('r') != std::string::npos)
1728 mask |= 0x00ff0000UL;
1729 if (mode.find('g') != std::string::npos)
1730 mask |= 0x0000ff00UL;
1731 if (mode.find('b') != std::string::npos)
1732 mask |= 0x000000ffUL;
1734 core::dimension2d<u32> dim = baseimg->getDimension();
1736 for (u32 y = 0; y < dim.Height; y++)
1737 for (u32 x = 0; x < dim.Width; x++)
1739 video::SColor c = baseimg->getPixel(x, y);
1741 baseimg->setPixel(x, y, c);
1746 Retrieves a tile at position X,Y (in tiles)
1747 from the base image it assumes to be a
1748 tilesheet with dimensions W,H (in tiles).
1750 else if (part_of_name.substr(0,7) == "[sheet:") {
1751 if (baseimg == NULL) {
1752 errorstream << "generateImagePart(): baseimg != NULL "
1753 << "for part_of_name=\"" << part_of_name
1754 << "\", cancelling." << std::endl;
1758 Strfnd sf(part_of_name);
1760 u32 w0 = stoi(sf.next("x"));
1761 u32 h0 = stoi(sf.next(":"));
1762 u32 x0 = stoi(sf.next(","));
1763 u32 y0 = stoi(sf.next(":"));
1765 core::dimension2d<u32> img_dim = baseimg->getDimension();
1766 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1768 video::IImage *img = driver->createImage(
1769 video::ECF_A8R8G8B8, tile_dim);
1771 errorstream << "generateImagePart(): Could not create image "
1772 << "for part_of_name=\"" << part_of_name
1773 << "\", cancelling." << std::endl;
1777 img->fill(video::SColor(0,0,0,0));
1778 v2u32 vdim(tile_dim);
1779 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1780 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1781 video::SColor(255,255,255,255), NULL);
1789 errorstream << "generateImagePart(): Invalid "
1790 " modification: \"" << part_of_name << "\"" << std::endl;
1798 Draw an image on top of an another one, using the alpha channel of the
1801 This exists because IImage::copyToWithAlpha() doesn't seem to always
1804 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1805 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1807 for (u32 y0=0; y0<size.Y; y0++)
1808 for (u32 x0=0; x0<size.X; x0++)
1810 s32 src_x = src_pos.X + x0;
1811 s32 src_y = src_pos.Y + y0;
1812 s32 dst_x = dst_pos.X + x0;
1813 s32 dst_y = dst_pos.Y + y0;
1814 video::SColor src_c = src->getPixel(src_x, src_y);
1815 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1816 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1817 dst->setPixel(dst_x, dst_y, dst_c);
1822 Draw an image on top of an another one, using the alpha channel of the
1823 source image; only modify fully opaque pixels in destinaion
1825 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1826 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1828 for (u32 y0=0; y0<size.Y; y0++)
1829 for (u32 x0=0; x0<size.X; x0++)
1831 s32 src_x = src_pos.X + x0;
1832 s32 src_y = src_pos.Y + y0;
1833 s32 dst_x = dst_pos.X + x0;
1834 s32 dst_y = dst_pos.Y + y0;
1835 video::SColor src_c = src->getPixel(src_x, src_y);
1836 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1837 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1839 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1840 dst->setPixel(dst_x, dst_y, dst_c);
1845 // This function has been disabled because it is currently unused.
1846 // Feel free to re-enable if you find it handy.
1849 Draw an image on top of an another one, using the specified ratio
1850 modify all partially-opaque pixels in the destination.
1852 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1853 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1855 for (u32 y0 = 0; y0 < size.Y; y0++)
1856 for (u32 x0 = 0; x0 < size.X; x0++)
1858 s32 src_x = src_pos.X + x0;
1859 s32 src_y = src_pos.Y + y0;
1860 s32 dst_x = dst_pos.X + x0;
1861 s32 dst_y = dst_pos.Y + y0;
1862 video::SColor src_c = src->getPixel(src_x, src_y);
1863 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1864 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1867 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1869 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1870 dst->setPixel(dst_x, dst_y, dst_c);
1877 Apply color to destination
1879 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1880 const video::SColor &color, int ratio, bool keep_alpha)
1882 u32 alpha = color.getAlpha();
1883 video::SColor dst_c;
1884 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1885 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1887 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1888 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1889 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1890 if (dst_alpha > 0) {
1891 dst_c.setAlpha(dst_alpha * alpha / 255);
1892 dst->setPixel(x, y, dst_c);
1895 } else { // replace the color including the alpha
1896 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1897 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1898 if (dst->getPixel(x, y).getAlpha() > 0)
1899 dst->setPixel(x, y, color);
1901 } else { // interpolate between the color and destination
1902 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1903 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1904 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1905 dst_c = dst->getPixel(x, y);
1906 if (dst_c.getAlpha() > 0) {
1907 dst_c = color.getInterpolated(dst_c, interp);
1908 dst->setPixel(x, y, dst_c);
1915 Apply color to destination
1917 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1918 const video::SColor &color)
1920 video::SColor dst_c;
1922 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1923 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1924 dst_c = dst->getPixel(x, y);
1927 (dst_c.getRed() * color.getRed()) / 255,
1928 (dst_c.getGreen() * color.getGreen()) / 255,
1929 (dst_c.getBlue() * color.getBlue()) / 255
1931 dst->setPixel(x, y, dst_c);
1936 Apply mask to destination
1938 static void apply_mask(video::IImage *mask, video::IImage *dst,
1939 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1941 for (u32 y0 = 0; y0 < size.Y; y0++) {
1942 for (u32 x0 = 0; x0 < size.X; x0++) {
1943 s32 mask_x = x0 + mask_pos.X;
1944 s32 mask_y = y0 + mask_pos.Y;
1945 s32 dst_x = x0 + dst_pos.X;
1946 s32 dst_y = y0 + dst_pos.Y;
1947 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1948 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1949 dst_c.color &= mask_c.color;
1950 dst->setPixel(dst_x, dst_y, dst_c);
1955 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
1956 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
1958 core::dimension2d<u32> strip_size = crack->getDimension();
1959 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
1960 core::dimension2d<u32> tile_size(size / tiles);
1961 s32 frame_count = strip_size.Height / strip_size.Width;
1962 if (frame_index >= frame_count)
1963 frame_index = frame_count - 1;
1964 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
1965 video::IImage *result = nullptr;
1967 // extract crack frame
1968 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
1971 if (tile_size == frame_size) {
1972 crack->copyTo(crack_tile, v2s32(0, 0), frame);
1974 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
1976 goto exit__has_tile;
1977 crack->copyTo(crack_frame, v2s32(0, 0), frame);
1978 crack_frame->copyToScaling(crack_tile);
1979 crack_frame->drop();
1985 result = driver->createImage(video::ECF_A8R8G8B8, size);
1987 goto exit__has_tile;
1989 for (u8 i = 0; i < tiles; i++)
1990 for (u8 j = 0; j < tiles; j++)
1991 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
1998 static void draw_crack(video::IImage *crack, video::IImage *dst,
1999 bool use_overlay, s32 frame_count, s32 progression,
2000 video::IVideoDriver *driver, u8 tiles)
2002 // Dimension of destination image
2003 core::dimension2d<u32> dim_dst = dst->getDimension();
2004 // Limit frame_count
2005 if (frame_count > (s32) dim_dst.Height)
2006 frame_count = dim_dst.Height;
2007 if (frame_count < 1)
2009 // Dimension of the scaled crack stage,
2010 // which is the same as the dimension of a single destination frame
2011 core::dimension2d<u32> frame_size(
2013 dim_dst.Height / frame_count
2015 video::IImage *crack_scaled = create_crack_image(crack, progression,
2016 frame_size, tiles, driver);
2020 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2021 for (s32 i = 0; i < frame_count; ++i) {
2022 v2s32 dst_pos(0, frame_size.Height * i);
2023 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2026 crack_scaled->drop();
2029 void brighten(video::IImage *image)
2034 core::dimension2d<u32> dim = image->getDimension();
2036 for (u32 y=0; y<dim.Height; y++)
2037 for (u32 x=0; x<dim.Width; x++)
2039 video::SColor c = image->getPixel(x,y);
2040 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2041 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2042 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2043 image->setPixel(x,y,c);
2047 u32 parseImageTransform(const std::string& s)
2049 int total_transform = 0;
2051 std::string transform_names[8];
2052 transform_names[0] = "i";
2053 transform_names[1] = "r90";
2054 transform_names[2] = "r180";
2055 transform_names[3] = "r270";
2056 transform_names[4] = "fx";
2057 transform_names[6] = "fy";
2059 std::size_t pos = 0;
2060 while(pos < s.size())
2063 for (int i = 0; i <= 7; ++i)
2065 const std::string &name_i = transform_names[i];
2067 if (s[pos] == ('0' + i))
2074 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2076 pos += name_i.size();
2083 // Multiply total_transform and transform in the group D4
2086 new_total = (transform + total_transform) % 4;
2088 new_total = (transform - total_transform + 8) % 4;
2089 if ((transform >= 4) ^ (total_transform >= 4))
2092 total_transform = new_total;
2094 return total_transform;
2097 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2099 if (transform % 2 == 0)
2102 return core::dimension2d<u32>(dim.Height, dim.Width);
2105 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2107 if (src == NULL || dst == NULL)
2110 core::dimension2d<u32> dstdim = dst->getDimension();
2113 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2114 assert(transform <= 7);
2117 Compute the transformation from source coordinates (sx,sy)
2118 to destination coordinates (dx,dy).
2122 if (transform == 0) // identity
2123 sxn = 0, syn = 2; // sx = dx, sy = dy
2124 else if (transform == 1) // rotate by 90 degrees ccw
2125 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2126 else if (transform == 2) // rotate by 180 degrees
2127 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2128 else if (transform == 3) // rotate by 270 degrees ccw
2129 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2130 else if (transform == 4) // flip x
2131 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2132 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2133 sxn = 2, syn = 0; // sx = dy, sy = dx
2134 else if (transform == 6) // flip y
2135 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2136 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2137 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2139 for (u32 dy=0; dy<dstdim.Height; dy++)
2140 for (u32 dx=0; dx<dstdim.Width; dx++)
2142 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2143 u32 sx = entries[sxn];
2144 u32 sy = entries[syn];
2145 video::SColor c = src->getPixel(sx,sy);
2146 dst->setPixel(dx,dy,c);
2150 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2152 if (isKnownSourceImage("override_normal.png"))
2153 return getTexture("override_normal.png");
2154 std::string fname_base = name;
2155 static const char *normal_ext = "_normal.png";
2156 static const u32 normal_ext_size = strlen(normal_ext);
2157 size_t pos = fname_base.find('.');
2158 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2159 if (isKnownSourceImage(fname_normal)) {
2160 // look for image extension and replace it
2162 while ((i = fname_base.find('.', i)) != std::string::npos) {
2163 fname_base.replace(i, 4, normal_ext);
2164 i += normal_ext_size;
2166 return getTexture(fname_base);
2171 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2173 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2174 video::SColor c(0, 0, 0, 0);
2175 video::ITexture *texture = getTexture(name);
2176 video::IImage *image = driver->createImage(texture,
2177 core::position2d<s32>(0, 0),
2178 texture->getOriginalSize());
2183 core::dimension2d<u32> dim = image->getDimension();
2186 step = dim.Width / 16;
2187 for (u16 x = 0; x < dim.Width; x += step) {
2188 for (u16 y = 0; y < dim.Width; y += step) {
2189 c = image->getPixel(x,y);
2190 if (c.getAlpha() > 0) {
2200 c.setRed(tR / total);
2201 c.setGreen(tG / total);
2202 c.setBlue(tB / total);
2209 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2211 std::string tname = "__shaderFlagsTexture";
2212 tname += normalmap_present ? "1" : "0";
2214 if (isKnownSourceImage(tname)) {
2215 return getTexture(tname);
2218 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2219 video::IImage *flags_image = driver->createImage(
2220 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2221 sanity_check(flags_image != NULL);
2222 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2223 flags_image->setPixel(0, 0, c);
2224 insertSourceImage(tname, flags_image);
2225 flags_image->drop();
2226 return getTexture(tname);
2230 std::vector<std::string> getTextureDirs()
2232 return fs::GetRecursiveDirs(g_settings->get("texture_path"));