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 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1017 // Only GLES2 is trusted to correctly report npot support
1018 if (get_GL_major_version() > 1 &&
1019 extensions.find("GL_OES_texture_npot") != std::string::npos) {
1023 unsigned int height = npot2(dim.Height);
1024 unsigned int width = npot2(dim.Width);
1026 if ((dim.Height == height) &&
1027 (dim.Width == width)) {
1031 if (dim.Height > height) {
1035 if (dim.Width > width) {
1039 video::IImage *targetimage =
1040 driver->createImage(video::ECF_A8R8G8B8,
1041 core::dimension2d<u32>(width, height));
1043 if (targetimage != NULL) {
1044 image->copyToScaling(targetimage);
1052 static std::string unescape_string(const std::string &str, const char esc = '\\')
1055 size_t pos = 0, cpos;
1056 out.reserve(str.size());
1058 cpos = str.find_first_of(esc, pos);
1059 if (cpos == std::string::npos) {
1060 out += str.substr(pos);
1063 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1069 bool TextureSource::generateImagePart(std::string part_of_name,
1070 video::IImage *& baseimg)
1072 const char escape = '\\'; // same as in generateImage()
1073 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1074 sanity_check(driver);
1076 // Stuff starting with [ are special commands
1077 if (part_of_name.empty() || part_of_name[0] != '[') {
1078 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1080 image = Align2Npot2(image, driver);
1082 if (image == NULL) {
1083 if (!part_of_name.empty()) {
1085 // Do not create normalmap dummies
1086 if (part_of_name.find("_normal.png") != std::string::npos) {
1087 warningstream << "generateImage(): Could not load normal map \""
1088 << part_of_name << "\"" << std::endl;
1092 errorstream << "generateImage(): Could not load image \""
1093 << part_of_name << "\" while building texture; "
1094 "Creating a dummy image" << std::endl;
1097 // Just create a dummy image
1098 //core::dimension2d<u32> dim(2,2);
1099 core::dimension2d<u32> dim(1,1);
1100 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1101 sanity_check(image != NULL);
1102 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1103 image->setPixel(1,0, video::SColor(255,0,255,0));
1104 image->setPixel(0,1, video::SColor(255,0,0,255));
1105 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1106 image->setPixel(0,0, video::SColor(255,myrand()%256,
1107 myrand()%256,myrand()%256));
1108 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1109 myrand()%256,myrand()%256));
1110 image->setPixel(0,1, video::SColor(255,myrand()%256,
1111 myrand()%256,myrand()%256));
1112 image->setPixel(1,1, video::SColor(255,myrand()%256,
1113 myrand()%256,myrand()%256));*/
1116 // If base image is NULL, load as base.
1117 if (baseimg == NULL)
1119 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1121 Copy it this way to get an alpha channel.
1122 Otherwise images with alpha cannot be blitted on
1123 images that don't have alpha in the original file.
1125 core::dimension2d<u32> dim = image->getDimension();
1126 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1127 image->copyTo(baseimg);
1129 // Else blit on base.
1132 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1133 // Size of the copied area
1134 core::dimension2d<u32> dim = image->getDimension();
1135 //core::dimension2d<u32> dim(16,16);
1136 // Position to copy the blitted to in the base image
1137 core::position2d<s32> pos_to(0,0);
1138 // Position to copy the blitted from in the blitted image
1139 core::position2d<s32> pos_from(0,0);
1141 /*image->copyToWithAlpha(baseimg, pos_to,
1142 core::rect<s32>(pos_from, dim),
1143 video::SColor(255,255,255,255),
1146 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1147 if (dim == dim_dst) {
1148 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1149 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1150 // Upscale overlying image
1151 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1152 createImage(video::ECF_A8R8G8B8, dim_dst);
1153 image->copyToScaling(scaled_image);
1155 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1156 scaled_image->drop();
1158 // Upscale base image
1159 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1160 createImage(video::ECF_A8R8G8B8, dim);
1161 baseimg->copyToScaling(scaled_base);
1163 baseimg = scaled_base;
1165 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1173 // A special texture modification
1175 /*infostream<<"generateImage(): generating special "
1176 <<"modification \""<<part_of_name<<"\""
1182 Adds a cracking texture
1183 N = animation frame count, P = crack progression
1185 if (str_starts_with(part_of_name, "[crack"))
1187 if (baseimg == NULL) {
1188 errorstream<<"generateImagePart(): baseimg == NULL "
1189 <<"for part_of_name=\""<<part_of_name
1190 <<"\", cancelling."<<std::endl;
1194 // Crack image number and overlay option
1195 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1196 bool use_overlay = (part_of_name[6] == 'o');
1197 Strfnd sf(part_of_name);
1199 s32 frame_count = stoi(sf.next(":"));
1200 s32 progression = stoi(sf.next(":"));
1202 // Check whether there is the <tiles> argument, that is,
1203 // whether there are 3 arguments. If so, shift values
1204 // as the first and not the last argument is optional.
1205 auto s = sf.next(":");
1207 tiles = frame_count;
1208 frame_count = progression;
1209 progression = stoi(s);
1212 if (progression >= 0) {
1216 It is an image with a number of cracking stages
1219 video::IImage *img_crack = m_sourcecache.getOrLoad(
1220 "crack_anylength.png");
1223 draw_crack(img_crack, baseimg,
1224 use_overlay, frame_count,
1225 progression, driver, tiles);
1231 [combine:WxH:X,Y=filename:X,Y=filename2
1232 Creates a bigger texture from any amount of smaller ones
1234 else if (str_starts_with(part_of_name, "[combine"))
1236 Strfnd sf(part_of_name);
1238 u32 w0 = stoi(sf.next("x"));
1239 u32 h0 = stoi(sf.next(":"));
1240 core::dimension2d<u32> dim(w0,h0);
1241 if (baseimg == NULL) {
1242 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1243 baseimg->fill(video::SColor(0,0,0,0));
1245 while (!sf.at_end()) {
1246 u32 x = stoi(sf.next(","));
1247 u32 y = stoi(sf.next("="));
1248 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1249 infostream<<"Adding \""<<filename
1250 <<"\" to combined ("<<x<<","<<y<<")"
1252 video::IImage *img = generateImage(filename);
1254 core::dimension2d<u32> dim = img->getDimension();
1255 infostream<<"Size "<<dim.Width
1256 <<"x"<<dim.Height<<std::endl;
1257 core::position2d<s32> pos_base(x, y);
1258 video::IImage *img2 =
1259 driver->createImage(video::ECF_A8R8G8B8, dim);
1262 /*img2->copyToWithAlpha(baseimg, pos_base,
1263 core::rect<s32>(v2s32(0,0), dim),
1264 video::SColor(255,255,255,255),
1266 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1269 errorstream << "generateImagePart(): Failed to load image \""
1270 << filename << "\" for [combine" << std::endl;
1277 else if (str_starts_with(part_of_name, "[brighten"))
1279 if (baseimg == NULL) {
1280 errorstream<<"generateImagePart(): baseimg==NULL "
1281 <<"for part_of_name=\""<<part_of_name
1282 <<"\", cancelling."<<std::endl;
1290 Make image completely opaque.
1291 Used for the leaves texture when in old leaves mode, so
1292 that the transparent parts don't look completely black
1293 when simple alpha channel is used for rendering.
1295 else if (str_starts_with(part_of_name, "[noalpha"))
1297 if (baseimg == NULL){
1298 errorstream<<"generateImagePart(): baseimg==NULL "
1299 <<"for part_of_name=\""<<part_of_name
1300 <<"\", cancelling."<<std::endl;
1304 core::dimension2d<u32> dim = baseimg->getDimension();
1306 // Set alpha to full
1307 for (u32 y=0; y<dim.Height; y++)
1308 for (u32 x=0; x<dim.Width; x++)
1310 video::SColor c = baseimg->getPixel(x,y);
1312 baseimg->setPixel(x,y,c);
1317 Convert one color to transparent.
1319 else if (str_starts_with(part_of_name, "[makealpha:"))
1321 if (baseimg == NULL) {
1322 errorstream<<"generateImagePart(): baseimg == NULL "
1323 <<"for part_of_name=\""<<part_of_name
1324 <<"\", cancelling."<<std::endl;
1328 Strfnd sf(part_of_name.substr(11));
1329 u32 r1 = stoi(sf.next(","));
1330 u32 g1 = stoi(sf.next(","));
1331 u32 b1 = stoi(sf.next(""));
1333 core::dimension2d<u32> dim = baseimg->getDimension();
1335 /*video::IImage *oldbaseimg = baseimg;
1336 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1337 oldbaseimg->copyTo(baseimg);
1338 oldbaseimg->drop();*/
1340 // Set alpha to full
1341 for (u32 y=0; y<dim.Height; y++)
1342 for (u32 x=0; x<dim.Width; x++)
1344 video::SColor c = baseimg->getPixel(x,y);
1346 u32 g = c.getGreen();
1347 u32 b = c.getBlue();
1348 if (!(r == r1 && g == g1 && b == b1))
1351 baseimg->setPixel(x,y,c);
1356 Rotates and/or flips the image.
1358 N can be a number (between 0 and 7) or a transform name.
1359 Rotations are counter-clockwise.
1361 1 R90 rotate by 90 degrees
1362 2 R180 rotate by 180 degrees
1363 3 R270 rotate by 270 degrees
1365 5 FXR90 flip X then rotate by 90 degrees
1367 7 FYR90 flip Y then rotate by 90 degrees
1369 Note: Transform names can be concatenated to produce
1370 their product (applies the first then the second).
1371 The resulting transform will be equivalent to one of the
1372 eight existing ones, though (see: dihedral group).
1374 else if (str_starts_with(part_of_name, "[transform"))
1376 if (baseimg == NULL) {
1377 errorstream<<"generateImagePart(): baseimg == NULL "
1378 <<"for part_of_name=\""<<part_of_name
1379 <<"\", cancelling."<<std::endl;
1383 u32 transform = parseImageTransform(part_of_name.substr(10));
1384 core::dimension2d<u32> dim = imageTransformDimension(
1385 transform, baseimg->getDimension());
1386 video::IImage *image = driver->createImage(
1387 baseimg->getColorFormat(), dim);
1388 sanity_check(image != NULL);
1389 imageTransform(transform, baseimg, image);
1394 [inventorycube{topimage{leftimage{rightimage
1395 In every subimage, replace ^ with &.
1396 Create an "inventory cube".
1397 NOTE: This should be used only on its own.
1398 Example (a grass block (not actually used in game):
1399 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1401 else if (str_starts_with(part_of_name, "[inventorycube"))
1403 if (baseimg != NULL){
1404 errorstream<<"generateImagePart(): baseimg != NULL "
1405 <<"for part_of_name=\""<<part_of_name
1406 <<"\", cancelling."<<std::endl;
1410 str_replace(part_of_name, '&', '^');
1411 Strfnd sf(part_of_name);
1413 std::string imagename_top = sf.next("{");
1414 std::string imagename_left = sf.next("{");
1415 std::string imagename_right = sf.next("{");
1417 // Generate images for the faces of the cube
1418 video::IImage *img_top = generateImage(imagename_top);
1419 video::IImage *img_left = generateImage(imagename_left);
1420 video::IImage *img_right = generateImage(imagename_right);
1422 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1423 errorstream << "generateImagePart(): Failed to create textures"
1424 << " for inventorycube \"" << part_of_name << "\""
1426 baseimg = generateImage(imagename_top);
1430 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1432 // Face images are not needed anymore
1440 [lowpart:percent:filename
1441 Adds the lower part of a texture
1443 else if (str_starts_with(part_of_name, "[lowpart:"))
1445 Strfnd sf(part_of_name);
1447 u32 percent = stoi(sf.next(":"));
1448 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1450 if (baseimg == NULL)
1451 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1452 video::IImage *img = generateImage(filename);
1455 core::dimension2d<u32> dim = img->getDimension();
1456 core::position2d<s32> pos_base(0, 0);
1457 video::IImage *img2 =
1458 driver->createImage(video::ECF_A8R8G8B8, dim);
1461 core::position2d<s32> clippos(0, 0);
1462 clippos.Y = dim.Height * (100-percent) / 100;
1463 core::dimension2d<u32> clipdim = dim;
1464 clipdim.Height = clipdim.Height * percent / 100 + 1;
1465 core::rect<s32> cliprect(clippos, clipdim);
1466 img2->copyToWithAlpha(baseimg, pos_base,
1467 core::rect<s32>(v2s32(0,0), dim),
1468 video::SColor(255,255,255,255),
1475 Crops a frame of a vertical animation.
1476 N = frame count, I = frame index
1478 else if (str_starts_with(part_of_name, "[verticalframe:"))
1480 Strfnd sf(part_of_name);
1482 u32 frame_count = stoi(sf.next(":"));
1483 u32 frame_index = stoi(sf.next(":"));
1485 if (baseimg == NULL){
1486 errorstream<<"generateImagePart(): baseimg != NULL "
1487 <<"for part_of_name=\""<<part_of_name
1488 <<"\", cancelling."<<std::endl;
1492 v2u32 frame_size = baseimg->getDimension();
1493 frame_size.Y /= frame_count;
1495 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1498 errorstream<<"generateImagePart(): Could not create image "
1499 <<"for part_of_name=\""<<part_of_name
1500 <<"\", cancelling."<<std::endl;
1504 // Fill target image with transparency
1505 img->fill(video::SColor(0,0,0,0));
1507 core::dimension2d<u32> dim = frame_size;
1508 core::position2d<s32> pos_dst(0, 0);
1509 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1510 baseimg->copyToWithAlpha(img, pos_dst,
1511 core::rect<s32>(pos_src, dim),
1512 video::SColor(255,255,255,255),
1520 Applies a mask to an image
1522 else if (str_starts_with(part_of_name, "[mask:"))
1524 if (baseimg == NULL) {
1525 errorstream << "generateImage(): baseimg == NULL "
1526 << "for part_of_name=\"" << part_of_name
1527 << "\", cancelling." << std::endl;
1530 Strfnd sf(part_of_name);
1532 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1534 video::IImage *img = generateImage(filename);
1536 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1537 img->getDimension());
1540 errorstream << "generateImage(): Failed to load \""
1541 << filename << "\".";
1546 multiplys a given color to any pixel of an image
1547 color = color as ColorString
1549 else if (str_starts_with(part_of_name, "[multiply:")) {
1550 Strfnd sf(part_of_name);
1552 std::string color_str = sf.next(":");
1554 if (baseimg == NULL) {
1555 errorstream << "generateImagePart(): baseimg != NULL "
1556 << "for part_of_name=\"" << part_of_name
1557 << "\", cancelling." << std::endl;
1561 video::SColor color;
1563 if (!parseColorString(color_str, color, false))
1566 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1570 Overlays image with given color
1571 color = color as ColorString
1573 else if (str_starts_with(part_of_name, "[colorize:"))
1575 Strfnd sf(part_of_name);
1577 std::string color_str = sf.next(":");
1578 std::string ratio_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 bool keep_alpha = false;
1591 if (!parseColorString(color_str, color, false))
1594 if (is_number(ratio_str))
1595 ratio = mystoi(ratio_str, 0, 255);
1596 else if (ratio_str == "alpha")
1599 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1602 [applyfiltersformesh
1605 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1607 // Apply the "clean transparent" filter, if configured.
1608 if (g_settings->getBool("texture_clean_transparent"))
1609 imageCleanTransparent(baseimg, 127);
1611 /* Upscale textures to user's requested minimum size. This is a trick to make
1612 * filters look as good on low-res textures as on high-res ones, by making
1613 * low-res textures BECOME high-res ones. This is helpful for worlds that
1614 * mix high- and low-res textures, or for mods with least-common-denominator
1615 * textures that don't have the resources to offer high-res alternatives.
1617 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1618 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1620 const core::dimension2d<u32> dim = baseimg->getDimension();
1622 /* Calculate scaling needed to make the shortest texture dimension
1623 * equal to the target minimum. If e.g. this is a vertical frames
1624 * animation, the short dimension will be the real size.
1626 if ((dim.Width == 0) || (dim.Height == 0)) {
1627 errorstream << "generateImagePart(): Illegal 0 dimension "
1628 << "for part_of_name=\""<< part_of_name
1629 << "\", cancelling." << std::endl;
1632 u32 xscale = scaleto / dim.Width;
1633 u32 yscale = scaleto / dim.Height;
1634 u32 scale = (xscale > yscale) ? xscale : yscale;
1636 // Never downscale; only scale up by 2x or more.
1638 u32 w = scale * dim.Width;
1639 u32 h = scale * dim.Height;
1640 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1641 video::IImage *newimg = driver->createImage(
1642 baseimg->getColorFormat(), newdim);
1643 baseimg->copyToScaling(newimg);
1651 Resizes the base image to the given dimensions
1653 else if (str_starts_with(part_of_name, "[resize"))
1655 if (baseimg == NULL) {
1656 errorstream << "generateImagePart(): baseimg == NULL "
1657 << "for part_of_name=\""<< part_of_name
1658 << "\", cancelling." << std::endl;
1662 Strfnd sf(part_of_name);
1664 u32 width = stoi(sf.next("x"));
1665 u32 height = stoi(sf.next(""));
1666 core::dimension2d<u32> dim(width, height);
1668 video::IImage *image = RenderingEngine::get_video_driver()->
1669 createImage(video::ECF_A8R8G8B8, dim);
1670 baseimg->copyToScaling(image);
1676 Makes the base image transparent according to the given ratio.
1677 R must be between 0 and 255.
1678 0 means totally transparent.
1679 255 means totally opaque.
1681 else if (str_starts_with(part_of_name, "[opacity:")) {
1682 if (baseimg == NULL) {
1683 errorstream << "generateImagePart(): baseimg == NULL "
1684 << "for part_of_name=\"" << part_of_name
1685 << "\", cancelling." << std::endl;
1689 Strfnd sf(part_of_name);
1692 u32 ratio = mystoi(sf.next(""), 0, 255);
1694 core::dimension2d<u32> dim = baseimg->getDimension();
1696 for (u32 y = 0; y < dim.Height; y++)
1697 for (u32 x = 0; x < dim.Width; x++)
1699 video::SColor c = baseimg->getPixel(x, y);
1700 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1701 baseimg->setPixel(x, y, c);
1706 Inverts the given channels of the base image.
1707 Mode may contain the characters "r", "g", "b", "a".
1708 Only the channels that are mentioned in the mode string
1711 else if (str_starts_with(part_of_name, "[invert:")) {
1712 if (baseimg == NULL) {
1713 errorstream << "generateImagePart(): baseimg == NULL "
1714 << "for part_of_name=\"" << part_of_name
1715 << "\", cancelling." << std::endl;
1719 Strfnd sf(part_of_name);
1722 std::string mode = sf.next("");
1724 if (mode.find('a') != std::string::npos)
1725 mask |= 0xff000000UL;
1726 if (mode.find('r') != std::string::npos)
1727 mask |= 0x00ff0000UL;
1728 if (mode.find('g') != std::string::npos)
1729 mask |= 0x0000ff00UL;
1730 if (mode.find('b') != std::string::npos)
1731 mask |= 0x000000ffUL;
1733 core::dimension2d<u32> dim = baseimg->getDimension();
1735 for (u32 y = 0; y < dim.Height; y++)
1736 for (u32 x = 0; x < dim.Width; x++)
1738 video::SColor c = baseimg->getPixel(x, y);
1740 baseimg->setPixel(x, y, c);
1745 Retrieves a tile at position X,Y (in tiles)
1746 from the base image it assumes to be a
1747 tilesheet with dimensions W,H (in tiles).
1749 else if (part_of_name.substr(0,7) == "[sheet:") {
1750 if (baseimg == NULL) {
1751 errorstream << "generateImagePart(): baseimg != NULL "
1752 << "for part_of_name=\"" << part_of_name
1753 << "\", cancelling." << std::endl;
1757 Strfnd sf(part_of_name);
1759 u32 w0 = stoi(sf.next("x"));
1760 u32 h0 = stoi(sf.next(":"));
1761 u32 x0 = stoi(sf.next(","));
1762 u32 y0 = stoi(sf.next(":"));
1764 core::dimension2d<u32> img_dim = baseimg->getDimension();
1765 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1767 video::IImage *img = driver->createImage(
1768 video::ECF_A8R8G8B8, tile_dim);
1770 errorstream << "generateImagePart(): Could not create image "
1771 << "for part_of_name=\"" << part_of_name
1772 << "\", cancelling." << std::endl;
1776 img->fill(video::SColor(0,0,0,0));
1777 v2u32 vdim(tile_dim);
1778 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1779 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1780 video::SColor(255,255,255,255), NULL);
1788 errorstream << "generateImagePart(): Invalid "
1789 " modification: \"" << part_of_name << "\"" << std::endl;
1797 Draw an image on top of an another one, using the alpha channel of the
1800 This exists because IImage::copyToWithAlpha() doesn't seem to always
1803 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1804 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1806 for (u32 y0=0; y0<size.Y; y0++)
1807 for (u32 x0=0; x0<size.X; x0++)
1809 s32 src_x = src_pos.X + x0;
1810 s32 src_y = src_pos.Y + y0;
1811 s32 dst_x = dst_pos.X + x0;
1812 s32 dst_y = dst_pos.Y + y0;
1813 video::SColor src_c = src->getPixel(src_x, src_y);
1814 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1815 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1816 dst->setPixel(dst_x, dst_y, dst_c);
1821 Draw an image on top of an another one, using the alpha channel of the
1822 source image; only modify fully opaque pixels in destinaion
1824 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1825 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1827 for (u32 y0=0; y0<size.Y; y0++)
1828 for (u32 x0=0; x0<size.X; x0++)
1830 s32 src_x = src_pos.X + x0;
1831 s32 src_y = src_pos.Y + y0;
1832 s32 dst_x = dst_pos.X + x0;
1833 s32 dst_y = dst_pos.Y + y0;
1834 video::SColor src_c = src->getPixel(src_x, src_y);
1835 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1836 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1838 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1839 dst->setPixel(dst_x, dst_y, dst_c);
1844 // This function has been disabled because it is currently unused.
1845 // Feel free to re-enable if you find it handy.
1848 Draw an image on top of an another one, using the specified ratio
1849 modify all partially-opaque pixels in the destination.
1851 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1852 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1854 for (u32 y0 = 0; y0 < size.Y; y0++)
1855 for (u32 x0 = 0; x0 < size.X; x0++)
1857 s32 src_x = src_pos.X + x0;
1858 s32 src_y = src_pos.Y + y0;
1859 s32 dst_x = dst_pos.X + x0;
1860 s32 dst_y = dst_pos.Y + y0;
1861 video::SColor src_c = src->getPixel(src_x, src_y);
1862 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1863 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1866 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1868 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1869 dst->setPixel(dst_x, dst_y, dst_c);
1876 Apply color to destination
1878 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1879 const video::SColor &color, int ratio, bool keep_alpha)
1881 u32 alpha = color.getAlpha();
1882 video::SColor dst_c;
1883 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1884 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1886 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1887 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1888 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1889 if (dst_alpha > 0) {
1890 dst_c.setAlpha(dst_alpha * alpha / 255);
1891 dst->setPixel(x, y, dst_c);
1894 } else { // replace the color including the alpha
1895 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1896 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1897 if (dst->getPixel(x, y).getAlpha() > 0)
1898 dst->setPixel(x, y, color);
1900 } else { // interpolate between the color and destination
1901 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1902 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1903 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1904 dst_c = dst->getPixel(x, y);
1905 if (dst_c.getAlpha() > 0) {
1906 dst_c = color.getInterpolated(dst_c, interp);
1907 dst->setPixel(x, y, dst_c);
1914 Apply color to destination
1916 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1917 const video::SColor &color)
1919 video::SColor dst_c;
1921 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1922 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1923 dst_c = dst->getPixel(x, y);
1926 (dst_c.getRed() * color.getRed()) / 255,
1927 (dst_c.getGreen() * color.getGreen()) / 255,
1928 (dst_c.getBlue() * color.getBlue()) / 255
1930 dst->setPixel(x, y, dst_c);
1935 Apply mask to destination
1937 static void apply_mask(video::IImage *mask, video::IImage *dst,
1938 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1940 for (u32 y0 = 0; y0 < size.Y; y0++) {
1941 for (u32 x0 = 0; x0 < size.X; x0++) {
1942 s32 mask_x = x0 + mask_pos.X;
1943 s32 mask_y = y0 + mask_pos.Y;
1944 s32 dst_x = x0 + dst_pos.X;
1945 s32 dst_y = y0 + dst_pos.Y;
1946 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1947 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1948 dst_c.color &= mask_c.color;
1949 dst->setPixel(dst_x, dst_y, dst_c);
1954 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
1955 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
1957 core::dimension2d<u32> strip_size = crack->getDimension();
1958 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
1959 core::dimension2d<u32> tile_size(size / tiles);
1960 s32 frame_count = strip_size.Height / strip_size.Width;
1961 if (frame_index >= frame_count)
1962 frame_index = frame_count - 1;
1963 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
1964 video::IImage *result = nullptr;
1966 // extract crack frame
1967 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
1970 if (tile_size == frame_size) {
1971 crack->copyTo(crack_tile, v2s32(0, 0), frame);
1973 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
1975 goto exit__has_tile;
1976 crack->copyTo(crack_frame, v2s32(0, 0), frame);
1977 crack_frame->copyToScaling(crack_tile);
1978 crack_frame->drop();
1984 result = driver->createImage(video::ECF_A8R8G8B8, size);
1986 goto exit__has_tile;
1988 for (u8 i = 0; i < tiles; i++)
1989 for (u8 j = 0; j < tiles; j++)
1990 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
1997 static void draw_crack(video::IImage *crack, video::IImage *dst,
1998 bool use_overlay, s32 frame_count, s32 progression,
1999 video::IVideoDriver *driver, u8 tiles)
2001 // Dimension of destination image
2002 core::dimension2d<u32> dim_dst = dst->getDimension();
2003 // Limit frame_count
2004 if (frame_count > (s32) dim_dst.Height)
2005 frame_count = dim_dst.Height;
2006 if (frame_count < 1)
2008 // Dimension of the scaled crack stage,
2009 // which is the same as the dimension of a single destination frame
2010 core::dimension2d<u32> frame_size(
2012 dim_dst.Height / frame_count
2014 video::IImage *crack_scaled = create_crack_image(crack, progression,
2015 frame_size, tiles, driver);
2019 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2020 for (s32 i = 0; i < frame_count; ++i) {
2021 v2s32 dst_pos(0, frame_size.Height * i);
2022 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2025 crack_scaled->drop();
2028 void brighten(video::IImage *image)
2033 core::dimension2d<u32> dim = image->getDimension();
2035 for (u32 y=0; y<dim.Height; y++)
2036 for (u32 x=0; x<dim.Width; x++)
2038 video::SColor c = image->getPixel(x,y);
2039 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2040 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2041 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2042 image->setPixel(x,y,c);
2046 u32 parseImageTransform(const std::string& s)
2048 int total_transform = 0;
2050 std::string transform_names[8];
2051 transform_names[0] = "i";
2052 transform_names[1] = "r90";
2053 transform_names[2] = "r180";
2054 transform_names[3] = "r270";
2055 transform_names[4] = "fx";
2056 transform_names[6] = "fy";
2058 std::size_t pos = 0;
2059 while(pos < s.size())
2062 for (int i = 0; i <= 7; ++i)
2064 const std::string &name_i = transform_names[i];
2066 if (s[pos] == ('0' + i))
2073 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2075 pos += name_i.size();
2082 // Multiply total_transform and transform in the group D4
2085 new_total = (transform + total_transform) % 4;
2087 new_total = (transform - total_transform + 8) % 4;
2088 if ((transform >= 4) ^ (total_transform >= 4))
2091 total_transform = new_total;
2093 return total_transform;
2096 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2098 if (transform % 2 == 0)
2101 return core::dimension2d<u32>(dim.Height, dim.Width);
2104 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2106 if (src == NULL || dst == NULL)
2109 core::dimension2d<u32> dstdim = dst->getDimension();
2112 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2113 assert(transform <= 7);
2116 Compute the transformation from source coordinates (sx,sy)
2117 to destination coordinates (dx,dy).
2121 if (transform == 0) // identity
2122 sxn = 0, syn = 2; // sx = dx, sy = dy
2123 else if (transform == 1) // rotate by 90 degrees ccw
2124 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2125 else if (transform == 2) // rotate by 180 degrees
2126 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2127 else if (transform == 3) // rotate by 270 degrees ccw
2128 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2129 else if (transform == 4) // flip x
2130 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2131 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2132 sxn = 2, syn = 0; // sx = dy, sy = dx
2133 else if (transform == 6) // flip y
2134 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2135 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2136 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2138 for (u32 dy=0; dy<dstdim.Height; dy++)
2139 for (u32 dx=0; dx<dstdim.Width; dx++)
2141 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2142 u32 sx = entries[sxn];
2143 u32 sy = entries[syn];
2144 video::SColor c = src->getPixel(sx,sy);
2145 dst->setPixel(dx,dy,c);
2149 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2151 if (isKnownSourceImage("override_normal.png"))
2152 return getTexture("override_normal.png");
2153 std::string fname_base = name;
2154 static const char *normal_ext = "_normal.png";
2155 static const u32 normal_ext_size = strlen(normal_ext);
2156 size_t pos = fname_base.find('.');
2157 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2158 if (isKnownSourceImage(fname_normal)) {
2159 // look for image extension and replace it
2161 while ((i = fname_base.find('.', i)) != std::string::npos) {
2162 fname_base.replace(i, 4, normal_ext);
2163 i += normal_ext_size;
2165 return getTexture(fname_base);
2170 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2172 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2173 video::SColor c(0, 0, 0, 0);
2174 video::ITexture *texture = getTexture(name);
2175 video::IImage *image = driver->createImage(texture,
2176 core::position2d<s32>(0, 0),
2177 texture->getOriginalSize());
2182 core::dimension2d<u32> dim = image->getDimension();
2185 step = dim.Width / 16;
2186 for (u16 x = 0; x < dim.Width; x += step) {
2187 for (u16 y = 0; y < dim.Width; y += step) {
2188 c = image->getPixel(x,y);
2189 if (c.getAlpha() > 0) {
2199 c.setRed(tR / total);
2200 c.setGreen(tG / total);
2201 c.setBlue(tB / total);
2208 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2210 std::string tname = "__shaderFlagsTexture";
2211 tname += normalmap_present ? "1" : "0";
2213 if (isKnownSourceImage(tname)) {
2214 return getTexture(tname);
2217 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2218 video::IImage *flags_image = driver->createImage(
2219 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2220 sanity_check(flags_image != NULL);
2221 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2222 flags_image->setPixel(0, 0, c);
2223 insertSourceImage(tname, flags_image);
2224 flags_image->drop();
2225 return getTexture(tname);
2229 std::vector<std::string> getTextureDirs()
2231 return fs::GetRecursiveDirs(g_settings->get("texture_path"));