3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <ICameraSceneNode.h>
24 #include <IrrCompileConfig.h>
25 #include "util/string.h"
26 #include "util/container.h"
27 #include "util/thread.h"
32 #include "util/strfnd.h"
33 #include "imagefilters.h"
34 #include "guiscalingfilter.h"
35 #include "renderingengine.h"
39 #ifdef _IRR_COMPILE_WITH_OGLES1_
42 #include <GLES2/gl2.h>
47 A cache from texture name to texture path
49 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
52 Replaces the filename extension.
54 std::string image = "a/image.png"
55 replace_ext(image, "jpg")
56 -> image = "a/image.jpg"
57 Returns true on success.
59 static bool replace_ext(std::string &path, const char *ext)
63 // Find place of last dot, fail if \ or / found.
65 for (s32 i=path.size()-1; i>=0; i--)
73 if (path[i] == '\\' || path[i] == '/')
76 // If not found, return an empty string
79 // Else make the new path
80 path = path.substr(0, last_dot_i+1) + ext;
85 Find out the full path of an image by trying different filename
90 std::string getImagePath(std::string path)
92 // A NULL-ended list of possible image extensions
93 const char *extensions[] = {
94 "png", "jpg", "bmp", "tga",
95 "pcx", "ppm", "psd", "wal", "rgb",
98 // If there is no extension, add one
99 if (removeStringEnd(path, extensions).empty())
100 path = path + ".png";
101 // Check paths until something is found to exist
102 const char **ext = extensions;
104 bool r = replace_ext(path, *ext);
107 if (fs::PathExists(path))
110 while((++ext) != NULL);
116 Gets the path to a texture by first checking if the texture exists
117 in texture_path and if not, using the data path.
119 Checks all supported extensions by replacing the original extension.
121 If not found, returns "".
123 Utilizes a thread-safe cache.
125 std::string getTexturePath(const std::string &filename, bool *is_base_pack)
127 std::string fullpath;
129 // This can set a wrong value on cached textures, but is irrelevant because
130 // is_base_pack is only passed when initializing the textures the first time
132 *is_base_pack = false;
136 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
141 Check from texture_path
143 for (const auto &path : getTextureDirs()) {
144 std::string testpath = path + DIR_DELIM;
145 testpath.append(filename);
146 // Check all filename extensions. Returns "" if not found.
147 fullpath = getImagePath(testpath);
148 if (!fullpath.empty())
153 Check from default data directory
155 if (fullpath.empty())
157 std::string base_path = porting::path_share + DIR_DELIM + "textures"
158 + DIR_DELIM + "base" + DIR_DELIM + "pack";
159 std::string testpath = base_path + DIR_DELIM + filename;
160 // Check all filename extensions. Returns "" if not found.
161 fullpath = getImagePath(testpath);
162 if (is_base_pack && !fullpath.empty())
163 *is_base_pack = true;
166 // Add to cache (also an empty result is cached)
167 g_texturename_to_path_cache.set(filename, fullpath);
173 void clearTextureNameCache()
175 g_texturename_to_path_cache.clear();
179 Stores internal information about a texture.
185 video::ITexture *texture;
188 const std::string &name_,
189 video::ITexture *texture_=NULL
198 SourceImageCache: A cache used for storing source images.
201 class SourceImageCache
204 ~SourceImageCache() {
205 for (auto &m_image : m_images) {
206 m_image.second->drop();
210 void insert(const std::string &name, video::IImage *img, bool prefer_local)
212 assert(img); // Pre-condition
214 std::map<std::string, video::IImage*>::iterator n;
215 n = m_images.find(name);
216 if (n != m_images.end()){
221 video::IImage* toadd = img;
222 bool need_to_grab = true;
224 // Try to use local texture instead if asked to
227 std::string path = getTexturePath(name, &is_base_pack);
229 if (!path.empty() && !is_base_pack) {
230 video::IImage *img2 = RenderingEngine::get_video_driver()->
231 createImageFromFile(path.c_str());
234 need_to_grab = false;
241 m_images[name] = toadd;
243 video::IImage* get(const std::string &name)
245 std::map<std::string, video::IImage*>::iterator n;
246 n = m_images.find(name);
247 if (n != m_images.end())
251 // Primarily fetches from cache, secondarily tries to read from filesystem
252 video::IImage *getOrLoad(const std::string &name)
254 std::map<std::string, video::IImage*>::iterator n;
255 n = m_images.find(name);
256 if (n != m_images.end()){
257 n->second->grab(); // Grab for caller
260 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
261 std::string path = getTexturePath(name);
263 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
264 <<name<<"\""<<std::endl;
267 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
269 video::IImage *img = driver->createImageFromFile(path.c_str());
272 m_images[name] = img;
273 img->grab(); // Grab for caller
278 std::map<std::string, video::IImage*> m_images;
285 class TextureSource : public IWritableTextureSource
289 virtual ~TextureSource();
293 Now, assume a texture with the id 1 exists, and has the name
294 "stone.png^mineral1".
295 Then a random thread calls getTextureId for a texture called
296 "stone.png^mineral1^crack0".
297 ...Now, WTF should happen? Well:
298 - getTextureId strips off stuff recursively from the end until
299 the remaining part is found, or nothing is left when
300 something is stripped out
302 But it is slow to search for textures by names and modify them
304 - ContentFeatures is made to contain ids for the basic plain
306 - Crack textures can be slow by themselves, but the framework
310 - Assume a texture with the id 1 exists, and has the name
311 "stone.png^mineral_coal.png".
312 - Now getNodeTile() stumbles upon a node which uses
313 texture id 1, and determines that MATERIAL_FLAG_CRACK
314 must be applied to the tile
315 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
316 has received the current crack level 0 from the client. It
317 finds out the name of the texture with getTextureName(1),
318 appends "^crack0" to it and gets a new texture id with
319 getTextureId("stone.png^mineral_coal.png^crack0").
324 Gets a texture id from cache or
325 - if main thread, generates the texture, adds to cache and returns id.
326 - if other thread, adds to request queue and waits for main thread.
328 The id 0 points to a NULL texture. It is returned in case of error.
330 u32 getTextureId(const std::string &name);
332 // Finds out the name of a cached texture.
333 std::string getTextureName(u32 id);
336 If texture specified by the name pointed by the id doesn't
337 exist, create it, then return the cached texture.
339 Can be called from any thread. If called from some other thread
340 and not found in cache, the call is queued to the main thread
343 video::ITexture* getTexture(u32 id);
345 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
348 Get a texture specifically intended for mesh
349 application, i.e. not HUD, compositing, or other 2D
350 use. This texture may be a different size and may
351 have had additional filters applied.
353 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
355 virtual Palette* getPalette(const std::string &name);
357 bool isKnownSourceImage(const std::string &name)
359 bool is_known = false;
360 bool cache_found = m_source_image_existence.get(name, &is_known);
363 // Not found in cache; find out if a local file exists
364 is_known = (!getTexturePath(name).empty());
365 m_source_image_existence.set(name, is_known);
369 // Processes queued texture requests from other threads.
370 // Shall be called from the main thread.
373 // Insert an image into the cache without touching the filesystem.
374 // Shall be called from the main thread.
375 void insertSourceImage(const std::string &name, video::IImage *img);
377 // Rebuild images and textures from the current set of source images
378 // Shall be called from the main thread.
379 void rebuildImagesAndTextures();
381 video::ITexture* getNormalTexture(const std::string &name);
382 video::SColor getTextureAverageColor(const std::string &name);
383 video::ITexture *getShaderFlagsTexture(bool normamap_present);
387 // The id of the thread that is allowed to use irrlicht directly
388 std::thread::id m_main_thread;
390 // Cache of source images
391 // This should be only accessed from the main thread
392 SourceImageCache m_sourcecache;
394 // Generate a texture
395 u32 generateTexture(const std::string &name);
397 // Generate image based on a string like "stone.png" or "[crack:1:0".
398 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
399 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
401 /*! Generates an image from a full string like
402 * "stone.png^mineral_coal.png^[crack:1:0".
403 * Shall be called from the main thread.
404 * The returned Image should be dropped.
406 video::IImage* generateImage(const std::string &name);
408 // Thread-safe cache of what source images are known (true = known)
409 MutexedMap<std::string, bool> m_source_image_existence;
411 // A texture id is index in this array.
412 // The first position contains a NULL texture.
413 std::vector<TextureInfo> m_textureinfo_cache;
414 // Maps a texture name to an index in the former.
415 std::map<std::string, u32> m_name_to_id;
416 // The two former containers are behind this mutex
417 std::mutex m_textureinfo_cache_mutex;
419 // Queued texture fetches (to be processed by the main thread)
420 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
422 // Textures that have been overwritten with other ones
423 // but can't be deleted because the ITexture* might still be used
424 std::vector<video::ITexture*> m_texture_trash;
426 // Maps image file names to loaded palettes.
427 std::unordered_map<std::string, Palette> m_palettes;
429 // Cached settings needed for making textures from meshes
430 bool m_setting_trilinear_filter;
431 bool m_setting_bilinear_filter;
432 bool m_setting_anisotropic_filter;
435 IWritableTextureSource *createTextureSource()
437 return new TextureSource();
440 TextureSource::TextureSource()
442 m_main_thread = std::this_thread::get_id();
444 // Add a NULL TextureInfo as the first index, named ""
445 m_textureinfo_cache.emplace_back("");
446 m_name_to_id[""] = 0;
448 // Cache some settings
449 // Note: Since this is only done once, the game must be restarted
450 // for these settings to take effect
451 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
452 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
453 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
456 TextureSource::~TextureSource()
458 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
460 unsigned int textures_before = driver->getTextureCount();
462 for (const auto &iter : m_textureinfo_cache) {
465 driver->removeTexture(iter.texture);
467 m_textureinfo_cache.clear();
469 for (auto t : m_texture_trash) {
470 //cleanup trashed texture
471 driver->removeTexture(t);
474 infostream << "~TextureSource() "<< textures_before << "/"
475 << driver->getTextureCount() << std::endl;
478 u32 TextureSource::getTextureId(const std::string &name)
480 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
484 See if texture already exists
486 MutexAutoLock lock(m_textureinfo_cache_mutex);
487 std::map<std::string, u32>::iterator n;
488 n = m_name_to_id.find(name);
489 if (n != m_name_to_id.end())
498 if (std::this_thread::get_id() == m_main_thread) {
499 return generateTexture(name);
503 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
505 // We're gonna ask the result to be put into here
506 static ResultQueue<std::string, u32, u8, u8> result_queue;
508 // Throw a request in
509 m_get_texture_queue.add(name, 0, 0, &result_queue);
513 // Wait result for a second
514 GetResult<std::string, u32, u8, u8>
515 result = result_queue.pop_front(1000);
517 if (result.key == name) {
521 } catch(ItemNotFoundException &e) {
522 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
526 infostream << "getTextureId(): Failed" << std::endl;
531 // Draw an image on top of an another one, using the alpha channel of the
533 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
534 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
536 // Like blit_with_alpha, but only modifies destination pixels that
538 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
539 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
541 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
542 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
543 // color alpha with the destination alpha.
544 // Otherwise, any pixels that are not fully transparent get the color alpha.
545 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
546 const video::SColor &color, int ratio, bool keep_alpha);
548 // paint a texture using the given color
549 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
550 const video::SColor &color);
552 // Apply a mask to an image
553 static void apply_mask(video::IImage *mask, video::IImage *dst,
554 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
556 // Draw or overlay a crack
557 static void draw_crack(video::IImage *crack, video::IImage *dst,
558 bool use_overlay, s32 frame_count, s32 progression,
559 video::IVideoDriver *driver, u8 tiles = 1);
562 void brighten(video::IImage *image);
563 // Parse a transform name
564 u32 parseImageTransform(const std::string& s);
565 // Apply transform to image dimension
566 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
567 // Apply transform to image data
568 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
571 This method generates all the textures
573 u32 TextureSource::generateTexture(const std::string &name)
575 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
577 // Empty name means texture 0
579 infostream<<"generateTexture(): name is empty"<<std::endl;
585 See if texture already exists
587 MutexAutoLock lock(m_textureinfo_cache_mutex);
588 std::map<std::string, u32>::iterator n;
589 n = m_name_to_id.find(name);
590 if (n != m_name_to_id.end()) {
596 Calling only allowed from main thread
598 if (std::this_thread::get_id() != m_main_thread) {
599 errorstream<<"TextureSource::generateTexture() "
600 "called not from main thread"<<std::endl;
604 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
605 sanity_check(driver);
607 video::IImage *img = generateImage(name);
609 video::ITexture *tex = NULL;
613 img = Align2Npot2(img, driver);
615 // Create texture from resulting image
616 tex = driver->addTexture(name.c_str(), img);
617 guiScalingCache(io::path(name.c_str()), driver, img);
622 Add texture to caches (add NULL textures too)
625 MutexAutoLock lock(m_textureinfo_cache_mutex);
627 u32 id = m_textureinfo_cache.size();
628 TextureInfo ti(name, tex);
629 m_textureinfo_cache.push_back(ti);
630 m_name_to_id[name] = id;
635 std::string TextureSource::getTextureName(u32 id)
637 MutexAutoLock lock(m_textureinfo_cache_mutex);
639 if (id >= m_textureinfo_cache.size())
641 errorstream<<"TextureSource::getTextureName(): id="<<id
642 <<" >= m_textureinfo_cache.size()="
643 <<m_textureinfo_cache.size()<<std::endl;
647 return m_textureinfo_cache[id].name;
650 video::ITexture* TextureSource::getTexture(u32 id)
652 MutexAutoLock lock(m_textureinfo_cache_mutex);
654 if (id >= m_textureinfo_cache.size())
657 return m_textureinfo_cache[id].texture;
660 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
662 u32 actual_id = getTextureId(name);
666 return getTexture(actual_id);
669 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
671 return getTexture(name + "^[applyfiltersformesh", id);
674 Palette* TextureSource::getPalette(const std::string &name)
676 // Only the main thread may load images
677 sanity_check(std::this_thread::get_id() == m_main_thread);
682 auto it = m_palettes.find(name);
683 if (it == m_palettes.end()) {
685 video::IImage *img = generateImage(name);
687 warningstream << "TextureSource::getPalette(): palette \"" << name
688 << "\" could not be loaded." << std::endl;
692 u32 w = img->getDimension().Width;
693 u32 h = img->getDimension().Height;
694 // Real area of the image
699 warningstream << "TextureSource::getPalette(): the specified"
700 << " palette image \"" << name << "\" is larger than 256"
701 << " pixels, using the first 256." << std::endl;
703 } else if (256 % area != 0)
704 warningstream << "TextureSource::getPalette(): the "
705 << "specified palette image \"" << name << "\" does not "
706 << "contain power of two pixels." << std::endl;
707 // We stretch the palette so it will fit 256 values
708 // This many param2 values will have the same color
709 u32 step = 256 / area;
710 // For each pixel in the image
711 for (u32 i = 0; i < area; i++) {
712 video::SColor c = img->getPixel(i % w, i / w);
713 // Fill in palette with 'step' colors
714 for (u32 j = 0; j < step; j++)
715 new_palette.push_back(c);
718 // Fill in remaining elements
719 while (new_palette.size() < 256)
720 new_palette.emplace_back(0xFFFFFFFF);
721 m_palettes[name] = new_palette;
722 it = m_palettes.find(name);
724 if (it != m_palettes.end())
725 return &((*it).second);
729 void TextureSource::processQueue()
734 //NOTE this is only thread safe for ONE consumer thread!
735 if (!m_get_texture_queue.empty())
737 GetRequest<std::string, u32, u8, u8>
738 request = m_get_texture_queue.pop();
740 /*infostream<<"TextureSource::processQueue(): "
741 <<"got texture request with "
742 <<"name=\""<<request.key<<"\""
745 m_get_texture_queue.pushResult(request, generateTexture(request.key));
749 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
751 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
753 sanity_check(std::this_thread::get_id() == m_main_thread);
755 m_sourcecache.insert(name, img, true);
756 m_source_image_existence.set(name, true);
759 void TextureSource::rebuildImagesAndTextures()
761 MutexAutoLock lock(m_textureinfo_cache_mutex);
763 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
764 sanity_check(driver);
767 for (TextureInfo &ti : m_textureinfo_cache) {
768 video::IImage *img = generateImage(ti.name);
770 img = Align2Npot2(img, driver);
772 // Create texture from resulting image
773 video::ITexture *t = NULL;
775 t = driver->addTexture(ti.name.c_str(), img);
776 guiScalingCache(io::path(ti.name.c_str()), driver, img);
779 video::ITexture *t_old = ti.texture;
784 m_texture_trash.push_back(t_old);
788 inline static void applyShadeFactor(video::SColor &color, u32 factor)
790 u32 f = core::clamp<u32>(factor, 0, 256);
791 color.setRed(color.getRed() * f / 256);
792 color.setGreen(color.getGreen() * f / 256);
793 color.setBlue(color.getBlue() * f / 256);
796 static video::IImage *createInventoryCubeImage(
797 video::IImage *top, video::IImage *left, video::IImage *right)
799 core::dimension2du size_top = top->getDimension();
800 core::dimension2du size_left = left->getDimension();
801 core::dimension2du size_right = right->getDimension();
803 u32 size = npot2(std::max({
804 size_top.Width, size_top.Height,
805 size_left.Width, size_left.Height,
806 size_right.Width, size_right.Height,
809 // It must be divisible by 4, to let everything work correctly.
810 // But it is a power of 2, so being at least 4 is the same.
811 // And the resulting texture should't be too large as well.
812 size = core::clamp<u32>(size, 4, 64);
814 // With such parameters, the cube fits exactly, touching each image line
815 // from `0` to `cube_size - 1`. (Note that division is exact here).
816 u32 cube_size = 9 * size;
817 u32 offset = size / 2;
819 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
821 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
823 core::dimension2du dim = image->getDimension();
824 video::ECOLOR_FORMAT format = image->getColorFormat();
825 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
826 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
827 image->copyToScaling(scaled);
831 sanity_check(image->getPitch() == 4 * size);
832 return reinterpret_cast<u32 *>(image->lock());
834 auto free_image = [] (video::IImage *image) -> void {
839 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
840 sanity_check(result->getPitch() == 4 * cube_size);
841 result->fill(video::SColor(0x00000000u));
842 u32 *target = reinterpret_cast<u32 *>(result->lock());
844 // Draws single cube face
845 // `shade_factor` is face brightness, in range [0.0, 1.0]
846 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
847 // `offsets` list pixels to be drawn for single source pixel
848 auto draw_image = [=] (video::IImage *image, float shade_factor,
849 s16 xu, s16 xv, s16 x1,
850 s16 yu, s16 yv, s16 y1,
851 std::initializer_list<v2s16> offsets) -> void {
852 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
853 const u32 *source = lock_image(image);
854 for (u16 v = 0; v < size; v++) {
855 for (u16 u = 0; u < size; u++) {
856 video::SColor pixel(*source);
857 applyShadeFactor(pixel, brightness);
858 s16 x = xu * u + xv * v + x1;
859 s16 y = yu * u + yv * v + y1;
860 for (const auto &off : offsets)
861 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
868 draw_image(top, 1.000000f,
869 4, -4, 4 * (size - 1),
872 {2, 0}, {3, 0}, {4, 0}, {5, 0},
873 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
874 {2, 2}, {3, 2}, {4, 2}, {5, 2},
877 draw_image(left, 0.836660f,
882 {0, 1}, {1, 1}, {2, 1}, {3, 1},
883 {0, 2}, {1, 2}, {2, 2}, {3, 2},
884 {0, 3}, {1, 3}, {2, 3}, {3, 3},
885 {0, 4}, {1, 4}, {2, 4}, {3, 4},
889 draw_image(right, 0.670820f,
894 {0, 1}, {1, 1}, {2, 1}, {3, 1},
895 {0, 2}, {1, 2}, {2, 2}, {3, 2},
896 {0, 3}, {1, 3}, {2, 3}, {3, 3},
897 {0, 4}, {1, 4}, {2, 4}, {3, 4},
905 video::IImage* TextureSource::generateImage(const std::string &name)
907 // Get the base image
909 const char separator = '^';
910 const char escape = '\\';
911 const char paren_open = '(';
912 const char paren_close = ')';
914 // Find last separator in the name
915 s32 last_separator_pos = -1;
917 for (s32 i = name.size() - 1; i >= 0; i--) {
918 if (i > 0 && name[i-1] == escape)
922 if (paren_bal == 0) {
923 last_separator_pos = i;
924 i = -1; // break out of loop
928 if (paren_bal == 0) {
929 errorstream << "generateImage(): unbalanced parentheses"
930 << "(extranous '(') while generating texture \""
931 << name << "\"" << std::endl;
944 errorstream << "generateImage(): unbalanced parentheses"
945 << "(missing matching '(') while generating texture \""
946 << name << "\"" << std::endl;
951 video::IImage *baseimg = NULL;
954 If separator was found, make the base image
955 using a recursive call.
957 if (last_separator_pos != -1) {
958 baseimg = generateImage(name.substr(0, last_separator_pos));
962 Parse out the last part of the name of the image and act
966 std::string last_part_of_name = name.substr(last_separator_pos + 1);
969 If this name is enclosed in parentheses, generate it
970 and blit it onto the base image
972 if (last_part_of_name[0] == paren_open
973 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
974 std::string name2 = last_part_of_name.substr(1,
975 last_part_of_name.size() - 2);
976 video::IImage *tmp = generateImage(name2);
978 errorstream << "generateImage(): "
979 "Failed to generate \"" << name2 << "\""
983 core::dimension2d<u32> dim = tmp->getDimension();
985 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
990 } else if (!generateImagePart(last_part_of_name, baseimg)) {
991 // Generate image according to part of name
992 errorstream << "generateImage(): "
993 "Failed to generate \"" << last_part_of_name << "\""
997 // If no resulting image, print a warning
998 if (baseimg == NULL) {
999 errorstream << "generateImage(): baseimg is NULL (attempted to"
1000 " create texture \"" << name << "\")" << std::endl;
1009 static inline u16 get_GL_major_version()
1011 const GLubyte *gl_version = glGetString(GL_VERSION);
1012 return (u16) (gl_version[0] - '0');
1016 * Check if hardware requires npot2 aligned textures
1017 * @return true if alignment NOT(!) requires, false otherwise
1020 bool hasNPotSupport()
1022 // Only GLES2 is trusted to correctly report npot support
1023 // Note: we cache the boolean result, the GL context will never change.
1024 static const bool supported = get_GL_major_version() > 1 &&
1025 glGetString(GL_EXTENSIONS) &&
1026 strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
1031 * Check and align image to npot2 if required by hardware
1032 * @param image image to check for npot2 alignment
1033 * @param driver driver to use for image operations
1034 * @return image or copy of image aligned to npot2
1037 video::IImage * Align2Npot2(video::IImage * image,
1038 video::IVideoDriver* driver)
1043 if (hasNPotSupport())
1046 core::dimension2d<u32> dim = image->getDimension();
1047 unsigned int height = npot2(dim.Height);
1048 unsigned int width = npot2(dim.Width);
1050 if (dim.Height == height && dim.Width == width)
1053 if (dim.Height > height)
1055 if (dim.Width > width)
1058 video::IImage *targetimage =
1059 driver->createImage(video::ECF_A8R8G8B8,
1060 core::dimension2d<u32>(width, height));
1062 if (targetimage != NULL)
1063 image->copyToScaling(targetimage);
1070 static std::string unescape_string(const std::string &str, const char esc = '\\')
1073 size_t pos = 0, cpos;
1074 out.reserve(str.size());
1076 cpos = str.find_first_of(esc, pos);
1077 if (cpos == std::string::npos) {
1078 out += str.substr(pos);
1081 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1087 bool TextureSource::generateImagePart(std::string part_of_name,
1088 video::IImage *& baseimg)
1090 const char escape = '\\'; // same as in generateImage()
1091 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1092 sanity_check(driver);
1094 // Stuff starting with [ are special commands
1095 if (part_of_name.empty() || part_of_name[0] != '[') {
1096 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1098 image = Align2Npot2(image, driver);
1100 if (image == NULL) {
1101 if (!part_of_name.empty()) {
1103 // Do not create normalmap dummies
1104 if (part_of_name.find("_normal.png") != std::string::npos) {
1105 warningstream << "generateImage(): Could not load normal map \""
1106 << part_of_name << "\"" << std::endl;
1110 errorstream << "generateImage(): Could not load image \""
1111 << part_of_name << "\" while building texture; "
1112 "Creating a dummy image" << std::endl;
1115 // Just create a dummy image
1116 //core::dimension2d<u32> dim(2,2);
1117 core::dimension2d<u32> dim(1,1);
1118 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1119 sanity_check(image != NULL);
1120 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1121 image->setPixel(1,0, video::SColor(255,0,255,0));
1122 image->setPixel(0,1, video::SColor(255,0,0,255));
1123 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1124 image->setPixel(0,0, video::SColor(255,myrand()%256,
1125 myrand()%256,myrand()%256));
1126 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1127 myrand()%256,myrand()%256));
1128 image->setPixel(0,1, video::SColor(255,myrand()%256,
1129 myrand()%256,myrand()%256));
1130 image->setPixel(1,1, video::SColor(255,myrand()%256,
1131 myrand()%256,myrand()%256));*/
1134 // If base image is NULL, load as base.
1135 if (baseimg == NULL)
1137 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1139 Copy it this way to get an alpha channel.
1140 Otherwise images with alpha cannot be blitted on
1141 images that don't have alpha in the original file.
1143 core::dimension2d<u32> dim = image->getDimension();
1144 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1145 image->copyTo(baseimg);
1147 // Else blit on base.
1150 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1151 // Size of the copied area
1152 core::dimension2d<u32> dim = image->getDimension();
1153 //core::dimension2d<u32> dim(16,16);
1154 // Position to copy the blitted to in the base image
1155 core::position2d<s32> pos_to(0,0);
1156 // Position to copy the blitted from in the blitted image
1157 core::position2d<s32> pos_from(0,0);
1159 /*image->copyToWithAlpha(baseimg, pos_to,
1160 core::rect<s32>(pos_from, dim),
1161 video::SColor(255,255,255,255),
1164 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1165 if (dim == dim_dst) {
1166 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1167 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1168 // Upscale overlying image
1169 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1170 createImage(video::ECF_A8R8G8B8, dim_dst);
1171 image->copyToScaling(scaled_image);
1173 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1174 scaled_image->drop();
1176 // Upscale base image
1177 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1178 createImage(video::ECF_A8R8G8B8, dim);
1179 baseimg->copyToScaling(scaled_base);
1181 baseimg = scaled_base;
1183 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1191 // A special texture modification
1193 /*infostream<<"generateImage(): generating special "
1194 <<"modification \""<<part_of_name<<"\""
1200 Adds a cracking texture
1201 N = animation frame count, P = crack progression
1203 if (str_starts_with(part_of_name, "[crack"))
1205 if (baseimg == NULL) {
1206 errorstream<<"generateImagePart(): baseimg == NULL "
1207 <<"for part_of_name=\""<<part_of_name
1208 <<"\", cancelling."<<std::endl;
1212 // Crack image number and overlay option
1213 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1214 bool use_overlay = (part_of_name[6] == 'o');
1215 Strfnd sf(part_of_name);
1217 s32 frame_count = stoi(sf.next(":"));
1218 s32 progression = stoi(sf.next(":"));
1220 // Check whether there is the <tiles> argument, that is,
1221 // whether there are 3 arguments. If so, shift values
1222 // as the first and not the last argument is optional.
1223 auto s = sf.next(":");
1225 tiles = frame_count;
1226 frame_count = progression;
1227 progression = stoi(s);
1230 if (progression >= 0) {
1234 It is an image with a number of cracking stages
1237 video::IImage *img_crack = m_sourcecache.getOrLoad(
1238 "crack_anylength.png");
1241 draw_crack(img_crack, baseimg,
1242 use_overlay, frame_count,
1243 progression, driver, tiles);
1249 [combine:WxH:X,Y=filename:X,Y=filename2
1250 Creates a bigger texture from any amount of smaller ones
1252 else if (str_starts_with(part_of_name, "[combine"))
1254 Strfnd sf(part_of_name);
1256 u32 w0 = stoi(sf.next("x"));
1257 u32 h0 = stoi(sf.next(":"));
1258 core::dimension2d<u32> dim(w0,h0);
1259 if (baseimg == NULL) {
1260 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1261 baseimg->fill(video::SColor(0,0,0,0));
1263 while (!sf.at_end()) {
1264 u32 x = stoi(sf.next(","));
1265 u32 y = stoi(sf.next("="));
1266 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1267 infostream<<"Adding \""<<filename
1268 <<"\" to combined ("<<x<<","<<y<<")"
1270 video::IImage *img = generateImage(filename);
1272 core::dimension2d<u32> dim = img->getDimension();
1273 infostream<<"Size "<<dim.Width
1274 <<"x"<<dim.Height<<std::endl;
1275 core::position2d<s32> pos_base(x, y);
1276 video::IImage *img2 =
1277 driver->createImage(video::ECF_A8R8G8B8, dim);
1280 /*img2->copyToWithAlpha(baseimg, pos_base,
1281 core::rect<s32>(v2s32(0,0), dim),
1282 video::SColor(255,255,255,255),
1284 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1287 errorstream << "generateImagePart(): Failed to load image \""
1288 << filename << "\" for [combine" << std::endl;
1295 else if (str_starts_with(part_of_name, "[brighten"))
1297 if (baseimg == NULL) {
1298 errorstream<<"generateImagePart(): baseimg==NULL "
1299 <<"for part_of_name=\""<<part_of_name
1300 <<"\", cancelling."<<std::endl;
1308 Make image completely opaque.
1309 Used for the leaves texture when in old leaves mode, so
1310 that the transparent parts don't look completely black
1311 when simple alpha channel is used for rendering.
1313 else if (str_starts_with(part_of_name, "[noalpha"))
1315 if (baseimg == NULL){
1316 errorstream<<"generateImagePart(): baseimg==NULL "
1317 <<"for part_of_name=\""<<part_of_name
1318 <<"\", cancelling."<<std::endl;
1322 core::dimension2d<u32> dim = baseimg->getDimension();
1324 // Set alpha to full
1325 for (u32 y=0; y<dim.Height; y++)
1326 for (u32 x=0; x<dim.Width; x++)
1328 video::SColor c = baseimg->getPixel(x,y);
1330 baseimg->setPixel(x,y,c);
1335 Convert one color to transparent.
1337 else if (str_starts_with(part_of_name, "[makealpha:"))
1339 if (baseimg == NULL) {
1340 errorstream<<"generateImagePart(): baseimg == NULL "
1341 <<"for part_of_name=\""<<part_of_name
1342 <<"\", cancelling."<<std::endl;
1346 Strfnd sf(part_of_name.substr(11));
1347 u32 r1 = stoi(sf.next(","));
1348 u32 g1 = stoi(sf.next(","));
1349 u32 b1 = stoi(sf.next(""));
1351 core::dimension2d<u32> dim = baseimg->getDimension();
1353 /*video::IImage *oldbaseimg = baseimg;
1354 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1355 oldbaseimg->copyTo(baseimg);
1356 oldbaseimg->drop();*/
1358 // Set alpha to full
1359 for (u32 y=0; y<dim.Height; y++)
1360 for (u32 x=0; x<dim.Width; x++)
1362 video::SColor c = baseimg->getPixel(x,y);
1364 u32 g = c.getGreen();
1365 u32 b = c.getBlue();
1366 if (!(r == r1 && g == g1 && b == b1))
1369 baseimg->setPixel(x,y,c);
1374 Rotates and/or flips the image.
1376 N can be a number (between 0 and 7) or a transform name.
1377 Rotations are counter-clockwise.
1379 1 R90 rotate by 90 degrees
1380 2 R180 rotate by 180 degrees
1381 3 R270 rotate by 270 degrees
1383 5 FXR90 flip X then rotate by 90 degrees
1385 7 FYR90 flip Y then rotate by 90 degrees
1387 Note: Transform names can be concatenated to produce
1388 their product (applies the first then the second).
1389 The resulting transform will be equivalent to one of the
1390 eight existing ones, though (see: dihedral group).
1392 else if (str_starts_with(part_of_name, "[transform"))
1394 if (baseimg == NULL) {
1395 errorstream<<"generateImagePart(): baseimg == NULL "
1396 <<"for part_of_name=\""<<part_of_name
1397 <<"\", cancelling."<<std::endl;
1401 u32 transform = parseImageTransform(part_of_name.substr(10));
1402 core::dimension2d<u32> dim = imageTransformDimension(
1403 transform, baseimg->getDimension());
1404 video::IImage *image = driver->createImage(
1405 baseimg->getColorFormat(), dim);
1406 sanity_check(image != NULL);
1407 imageTransform(transform, baseimg, image);
1412 [inventorycube{topimage{leftimage{rightimage
1413 In every subimage, replace ^ with &.
1414 Create an "inventory cube".
1415 NOTE: This should be used only on its own.
1416 Example (a grass block (not actually used in game):
1417 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1419 else if (str_starts_with(part_of_name, "[inventorycube"))
1421 if (baseimg != NULL){
1422 errorstream<<"generateImagePart(): baseimg != NULL "
1423 <<"for part_of_name=\""<<part_of_name
1424 <<"\", cancelling."<<std::endl;
1428 str_replace(part_of_name, '&', '^');
1429 Strfnd sf(part_of_name);
1431 std::string imagename_top = sf.next("{");
1432 std::string imagename_left = sf.next("{");
1433 std::string imagename_right = sf.next("{");
1435 // Generate images for the faces of the cube
1436 video::IImage *img_top = generateImage(imagename_top);
1437 video::IImage *img_left = generateImage(imagename_left);
1438 video::IImage *img_right = generateImage(imagename_right);
1440 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1441 errorstream << "generateImagePart(): Failed to create textures"
1442 << " for inventorycube \"" << part_of_name << "\""
1444 baseimg = generateImage(imagename_top);
1448 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1450 // Face images are not needed anymore
1458 [lowpart:percent:filename
1459 Adds the lower part of a texture
1461 else if (str_starts_with(part_of_name, "[lowpart:"))
1463 Strfnd sf(part_of_name);
1465 u32 percent = stoi(sf.next(":"));
1466 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1468 if (baseimg == NULL)
1469 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1470 video::IImage *img = generateImage(filename);
1473 core::dimension2d<u32> dim = img->getDimension();
1474 core::position2d<s32> pos_base(0, 0);
1475 video::IImage *img2 =
1476 driver->createImage(video::ECF_A8R8G8B8, dim);
1479 core::position2d<s32> clippos(0, 0);
1480 clippos.Y = dim.Height * (100-percent) / 100;
1481 core::dimension2d<u32> clipdim = dim;
1482 clipdim.Height = clipdim.Height * percent / 100 + 1;
1483 core::rect<s32> cliprect(clippos, clipdim);
1484 img2->copyToWithAlpha(baseimg, pos_base,
1485 core::rect<s32>(v2s32(0,0), dim),
1486 video::SColor(255,255,255,255),
1493 Crops a frame of a vertical animation.
1494 N = frame count, I = frame index
1496 else if (str_starts_with(part_of_name, "[verticalframe:"))
1498 Strfnd sf(part_of_name);
1500 u32 frame_count = stoi(sf.next(":"));
1501 u32 frame_index = stoi(sf.next(":"));
1503 if (baseimg == NULL){
1504 errorstream<<"generateImagePart(): baseimg != NULL "
1505 <<"for part_of_name=\""<<part_of_name
1506 <<"\", cancelling."<<std::endl;
1510 v2u32 frame_size = baseimg->getDimension();
1511 frame_size.Y /= frame_count;
1513 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1516 errorstream<<"generateImagePart(): Could not create image "
1517 <<"for part_of_name=\""<<part_of_name
1518 <<"\", cancelling."<<std::endl;
1522 // Fill target image with transparency
1523 img->fill(video::SColor(0,0,0,0));
1525 core::dimension2d<u32> dim = frame_size;
1526 core::position2d<s32> pos_dst(0, 0);
1527 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1528 baseimg->copyToWithAlpha(img, pos_dst,
1529 core::rect<s32>(pos_src, dim),
1530 video::SColor(255,255,255,255),
1538 Applies a mask to an image
1540 else if (str_starts_with(part_of_name, "[mask:"))
1542 if (baseimg == NULL) {
1543 errorstream << "generateImage(): baseimg == NULL "
1544 << "for part_of_name=\"" << part_of_name
1545 << "\", cancelling." << std::endl;
1548 Strfnd sf(part_of_name);
1550 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1552 video::IImage *img = generateImage(filename);
1554 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1555 img->getDimension());
1558 errorstream << "generateImage(): Failed to load \""
1559 << filename << "\".";
1564 multiplys a given color to any pixel of an image
1565 color = color as ColorString
1567 else if (str_starts_with(part_of_name, "[multiply:")) {
1568 Strfnd sf(part_of_name);
1570 std::string color_str = sf.next(":");
1572 if (baseimg == NULL) {
1573 errorstream << "generateImagePart(): baseimg != NULL "
1574 << "for part_of_name=\"" << part_of_name
1575 << "\", cancelling." << std::endl;
1579 video::SColor color;
1581 if (!parseColorString(color_str, color, false))
1584 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1588 Overlays image with given color
1589 color = color as ColorString
1591 else if (str_starts_with(part_of_name, "[colorize:"))
1593 Strfnd sf(part_of_name);
1595 std::string color_str = sf.next(":");
1596 std::string ratio_str = sf.next(":");
1598 if (baseimg == NULL) {
1599 errorstream << "generateImagePart(): baseimg != NULL "
1600 << "for part_of_name=\"" << part_of_name
1601 << "\", cancelling." << std::endl;
1605 video::SColor color;
1607 bool keep_alpha = false;
1609 if (!parseColorString(color_str, color, false))
1612 if (is_number(ratio_str))
1613 ratio = mystoi(ratio_str, 0, 255);
1614 else if (ratio_str == "alpha")
1617 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1620 [applyfiltersformesh
1623 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1625 // Apply the "clean transparent" filter, if configured.
1626 if (g_settings->getBool("texture_clean_transparent"))
1627 imageCleanTransparent(baseimg, 127);
1629 /* Upscale textures to user's requested minimum size. This is a trick to make
1630 * filters look as good on low-res textures as on high-res ones, by making
1631 * low-res textures BECOME high-res ones. This is helpful for worlds that
1632 * mix high- and low-res textures, or for mods with least-common-denominator
1633 * textures that don't have the resources to offer high-res alternatives.
1635 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1636 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1638 const core::dimension2d<u32> dim = baseimg->getDimension();
1640 /* Calculate scaling needed to make the shortest texture dimension
1641 * equal to the target minimum. If e.g. this is a vertical frames
1642 * animation, the short dimension will be the real size.
1644 if ((dim.Width == 0) || (dim.Height == 0)) {
1645 errorstream << "generateImagePart(): Illegal 0 dimension "
1646 << "for part_of_name=\""<< part_of_name
1647 << "\", cancelling." << std::endl;
1650 u32 xscale = scaleto / dim.Width;
1651 u32 yscale = scaleto / dim.Height;
1652 u32 scale = (xscale > yscale) ? xscale : yscale;
1654 // Never downscale; only scale up by 2x or more.
1656 u32 w = scale * dim.Width;
1657 u32 h = scale * dim.Height;
1658 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1659 video::IImage *newimg = driver->createImage(
1660 baseimg->getColorFormat(), newdim);
1661 baseimg->copyToScaling(newimg);
1669 Resizes the base image to the given dimensions
1671 else if (str_starts_with(part_of_name, "[resize"))
1673 if (baseimg == NULL) {
1674 errorstream << "generateImagePart(): baseimg == NULL "
1675 << "for part_of_name=\""<< part_of_name
1676 << "\", cancelling." << std::endl;
1680 Strfnd sf(part_of_name);
1682 u32 width = stoi(sf.next("x"));
1683 u32 height = stoi(sf.next(""));
1684 core::dimension2d<u32> dim(width, height);
1686 video::IImage *image = RenderingEngine::get_video_driver()->
1687 createImage(video::ECF_A8R8G8B8, dim);
1688 baseimg->copyToScaling(image);
1694 Makes the base image transparent according to the given ratio.
1695 R must be between 0 and 255.
1696 0 means totally transparent.
1697 255 means totally opaque.
1699 else if (str_starts_with(part_of_name, "[opacity:")) {
1700 if (baseimg == NULL) {
1701 errorstream << "generateImagePart(): baseimg == NULL "
1702 << "for part_of_name=\"" << part_of_name
1703 << "\", cancelling." << std::endl;
1707 Strfnd sf(part_of_name);
1710 u32 ratio = mystoi(sf.next(""), 0, 255);
1712 core::dimension2d<u32> dim = baseimg->getDimension();
1714 for (u32 y = 0; y < dim.Height; y++)
1715 for (u32 x = 0; x < dim.Width; x++)
1717 video::SColor c = baseimg->getPixel(x, y);
1718 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1719 baseimg->setPixel(x, y, c);
1724 Inverts the given channels of the base image.
1725 Mode may contain the characters "r", "g", "b", "a".
1726 Only the channels that are mentioned in the mode string
1729 else if (str_starts_with(part_of_name, "[invert:")) {
1730 if (baseimg == NULL) {
1731 errorstream << "generateImagePart(): baseimg == NULL "
1732 << "for part_of_name=\"" << part_of_name
1733 << "\", cancelling." << std::endl;
1737 Strfnd sf(part_of_name);
1740 std::string mode = sf.next("");
1742 if (mode.find('a') != std::string::npos)
1743 mask |= 0xff000000UL;
1744 if (mode.find('r') != std::string::npos)
1745 mask |= 0x00ff0000UL;
1746 if (mode.find('g') != std::string::npos)
1747 mask |= 0x0000ff00UL;
1748 if (mode.find('b') != std::string::npos)
1749 mask |= 0x000000ffUL;
1751 core::dimension2d<u32> dim = baseimg->getDimension();
1753 for (u32 y = 0; y < dim.Height; y++)
1754 for (u32 x = 0; x < dim.Width; x++)
1756 video::SColor c = baseimg->getPixel(x, y);
1758 baseimg->setPixel(x, y, c);
1763 Retrieves a tile at position X,Y (in tiles)
1764 from the base image it assumes to be a
1765 tilesheet with dimensions W,H (in tiles).
1767 else if (part_of_name.substr(0,7) == "[sheet:") {
1768 if (baseimg == NULL) {
1769 errorstream << "generateImagePart(): baseimg != NULL "
1770 << "for part_of_name=\"" << part_of_name
1771 << "\", cancelling." << std::endl;
1775 Strfnd sf(part_of_name);
1777 u32 w0 = stoi(sf.next("x"));
1778 u32 h0 = stoi(sf.next(":"));
1779 u32 x0 = stoi(sf.next(","));
1780 u32 y0 = stoi(sf.next(":"));
1782 core::dimension2d<u32> img_dim = baseimg->getDimension();
1783 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1785 video::IImage *img = driver->createImage(
1786 video::ECF_A8R8G8B8, tile_dim);
1788 errorstream << "generateImagePart(): Could not create image "
1789 << "for part_of_name=\"" << part_of_name
1790 << "\", cancelling." << std::endl;
1794 img->fill(video::SColor(0,0,0,0));
1795 v2u32 vdim(tile_dim);
1796 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1797 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1798 video::SColor(255,255,255,255), NULL);
1806 errorstream << "generateImagePart(): Invalid "
1807 " modification: \"" << part_of_name << "\"" << std::endl;
1815 Draw an image on top of an another one, using the alpha channel of the
1818 This exists because IImage::copyToWithAlpha() doesn't seem to always
1821 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1822 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1824 for (u32 y0=0; y0<size.Y; y0++)
1825 for (u32 x0=0; x0<size.X; x0++)
1827 s32 src_x = src_pos.X + x0;
1828 s32 src_y = src_pos.Y + y0;
1829 s32 dst_x = dst_pos.X + x0;
1830 s32 dst_y = dst_pos.Y + y0;
1831 video::SColor src_c = src->getPixel(src_x, src_y);
1832 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1833 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1834 dst->setPixel(dst_x, dst_y, dst_c);
1839 Draw an image on top of an another one, using the alpha channel of the
1840 source image; only modify fully opaque pixels in destinaion
1842 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1843 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1845 for (u32 y0=0; y0<size.Y; y0++)
1846 for (u32 x0=0; x0<size.X; x0++)
1848 s32 src_x = src_pos.X + x0;
1849 s32 src_y = src_pos.Y + y0;
1850 s32 dst_x = dst_pos.X + x0;
1851 s32 dst_y = dst_pos.Y + y0;
1852 video::SColor src_c = src->getPixel(src_x, src_y);
1853 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1854 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1856 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1857 dst->setPixel(dst_x, dst_y, dst_c);
1862 // This function has been disabled because it is currently unused.
1863 // Feel free to re-enable if you find it handy.
1866 Draw an image on top of an another one, using the specified ratio
1867 modify all partially-opaque pixels in the destination.
1869 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1870 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1872 for (u32 y0 = 0; y0 < size.Y; y0++)
1873 for (u32 x0 = 0; x0 < size.X; x0++)
1875 s32 src_x = src_pos.X + x0;
1876 s32 src_y = src_pos.Y + y0;
1877 s32 dst_x = dst_pos.X + x0;
1878 s32 dst_y = dst_pos.Y + y0;
1879 video::SColor src_c = src->getPixel(src_x, src_y);
1880 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1881 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1884 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1886 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1887 dst->setPixel(dst_x, dst_y, dst_c);
1894 Apply color to destination
1896 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1897 const video::SColor &color, int ratio, bool keep_alpha)
1899 u32 alpha = color.getAlpha();
1900 video::SColor dst_c;
1901 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1902 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1904 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1905 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1906 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1907 if (dst_alpha > 0) {
1908 dst_c.setAlpha(dst_alpha * alpha / 255);
1909 dst->setPixel(x, y, dst_c);
1912 } else { // replace the color including the alpha
1913 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1914 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1915 if (dst->getPixel(x, y).getAlpha() > 0)
1916 dst->setPixel(x, y, color);
1918 } else { // interpolate between the color and destination
1919 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1920 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1921 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1922 dst_c = dst->getPixel(x, y);
1923 if (dst_c.getAlpha() > 0) {
1924 dst_c = color.getInterpolated(dst_c, interp);
1925 dst->setPixel(x, y, dst_c);
1932 Apply color to destination
1934 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1935 const video::SColor &color)
1937 video::SColor dst_c;
1939 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1940 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1941 dst_c = dst->getPixel(x, y);
1944 (dst_c.getRed() * color.getRed()) / 255,
1945 (dst_c.getGreen() * color.getGreen()) / 255,
1946 (dst_c.getBlue() * color.getBlue()) / 255
1948 dst->setPixel(x, y, dst_c);
1953 Apply mask to destination
1955 static void apply_mask(video::IImage *mask, video::IImage *dst,
1956 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1958 for (u32 y0 = 0; y0 < size.Y; y0++) {
1959 for (u32 x0 = 0; x0 < size.X; x0++) {
1960 s32 mask_x = x0 + mask_pos.X;
1961 s32 mask_y = y0 + mask_pos.Y;
1962 s32 dst_x = x0 + dst_pos.X;
1963 s32 dst_y = y0 + dst_pos.Y;
1964 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1965 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1966 dst_c.color &= mask_c.color;
1967 dst->setPixel(dst_x, dst_y, dst_c);
1972 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
1973 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
1975 core::dimension2d<u32> strip_size = crack->getDimension();
1976 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
1977 core::dimension2d<u32> tile_size(size / tiles);
1978 s32 frame_count = strip_size.Height / strip_size.Width;
1979 if (frame_index >= frame_count)
1980 frame_index = frame_count - 1;
1981 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
1982 video::IImage *result = nullptr;
1984 // extract crack frame
1985 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
1988 if (tile_size == frame_size) {
1989 crack->copyTo(crack_tile, v2s32(0, 0), frame);
1991 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
1993 goto exit__has_tile;
1994 crack->copyTo(crack_frame, v2s32(0, 0), frame);
1995 crack_frame->copyToScaling(crack_tile);
1996 crack_frame->drop();
2002 result = driver->createImage(video::ECF_A8R8G8B8, size);
2004 goto exit__has_tile;
2006 for (u8 i = 0; i < tiles; i++)
2007 for (u8 j = 0; j < tiles; j++)
2008 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2015 static void draw_crack(video::IImage *crack, video::IImage *dst,
2016 bool use_overlay, s32 frame_count, s32 progression,
2017 video::IVideoDriver *driver, u8 tiles)
2019 // Dimension of destination image
2020 core::dimension2d<u32> dim_dst = dst->getDimension();
2021 // Limit frame_count
2022 if (frame_count > (s32) dim_dst.Height)
2023 frame_count = dim_dst.Height;
2024 if (frame_count < 1)
2026 // Dimension of the scaled crack stage,
2027 // which is the same as the dimension of a single destination frame
2028 core::dimension2d<u32> frame_size(
2030 dim_dst.Height / frame_count
2032 video::IImage *crack_scaled = create_crack_image(crack, progression,
2033 frame_size, tiles, driver);
2037 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2038 for (s32 i = 0; i < frame_count; ++i) {
2039 v2s32 dst_pos(0, frame_size.Height * i);
2040 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2043 crack_scaled->drop();
2046 void brighten(video::IImage *image)
2051 core::dimension2d<u32> dim = image->getDimension();
2053 for (u32 y=0; y<dim.Height; y++)
2054 for (u32 x=0; x<dim.Width; x++)
2056 video::SColor c = image->getPixel(x,y);
2057 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2058 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2059 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2060 image->setPixel(x,y,c);
2064 u32 parseImageTransform(const std::string& s)
2066 int total_transform = 0;
2068 std::string transform_names[8];
2069 transform_names[0] = "i";
2070 transform_names[1] = "r90";
2071 transform_names[2] = "r180";
2072 transform_names[3] = "r270";
2073 transform_names[4] = "fx";
2074 transform_names[6] = "fy";
2076 std::size_t pos = 0;
2077 while(pos < s.size())
2080 for (int i = 0; i <= 7; ++i)
2082 const std::string &name_i = transform_names[i];
2084 if (s[pos] == ('0' + i))
2091 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2093 pos += name_i.size();
2100 // Multiply total_transform and transform in the group D4
2103 new_total = (transform + total_transform) % 4;
2105 new_total = (transform - total_transform + 8) % 4;
2106 if ((transform >= 4) ^ (total_transform >= 4))
2109 total_transform = new_total;
2111 return total_transform;
2114 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2116 if (transform % 2 == 0)
2119 return core::dimension2d<u32>(dim.Height, dim.Width);
2122 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2124 if (src == NULL || dst == NULL)
2127 core::dimension2d<u32> dstdim = dst->getDimension();
2130 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2131 assert(transform <= 7);
2134 Compute the transformation from source coordinates (sx,sy)
2135 to destination coordinates (dx,dy).
2139 if (transform == 0) // identity
2140 sxn = 0, syn = 2; // sx = dx, sy = dy
2141 else if (transform == 1) // rotate by 90 degrees ccw
2142 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2143 else if (transform == 2) // rotate by 180 degrees
2144 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2145 else if (transform == 3) // rotate by 270 degrees ccw
2146 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2147 else if (transform == 4) // flip x
2148 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2149 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2150 sxn = 2, syn = 0; // sx = dy, sy = dx
2151 else if (transform == 6) // flip y
2152 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2153 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2154 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2156 for (u32 dy=0; dy<dstdim.Height; dy++)
2157 for (u32 dx=0; dx<dstdim.Width; dx++)
2159 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2160 u32 sx = entries[sxn];
2161 u32 sy = entries[syn];
2162 video::SColor c = src->getPixel(sx,sy);
2163 dst->setPixel(dx,dy,c);
2167 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2169 if (isKnownSourceImage("override_normal.png"))
2170 return getTexture("override_normal.png");
2171 std::string fname_base = name;
2172 static const char *normal_ext = "_normal.png";
2173 static const u32 normal_ext_size = strlen(normal_ext);
2174 size_t pos = fname_base.find('.');
2175 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2176 if (isKnownSourceImage(fname_normal)) {
2177 // look for image extension and replace it
2179 while ((i = fname_base.find('.', i)) != std::string::npos) {
2180 fname_base.replace(i, 4, normal_ext);
2181 i += normal_ext_size;
2183 return getTexture(fname_base);
2188 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2190 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2191 video::SColor c(0, 0, 0, 0);
2192 video::ITexture *texture = getTexture(name);
2193 video::IImage *image = driver->createImage(texture,
2194 core::position2d<s32>(0, 0),
2195 texture->getOriginalSize());
2200 core::dimension2d<u32> dim = image->getDimension();
2203 step = dim.Width / 16;
2204 for (u16 x = 0; x < dim.Width; x += step) {
2205 for (u16 y = 0; y < dim.Width; y += step) {
2206 c = image->getPixel(x,y);
2207 if (c.getAlpha() > 0) {
2217 c.setRed(tR / total);
2218 c.setGreen(tG / total);
2219 c.setBlue(tB / total);
2226 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2228 std::string tname = "__shaderFlagsTexture";
2229 tname += normalmap_present ? "1" : "0";
2231 if (isKnownSourceImage(tname)) {
2232 return getTexture(tname);
2235 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2236 video::IImage *flags_image = driver->createImage(
2237 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2238 sanity_check(flags_image != NULL);
2239 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2240 flags_image->setPixel(0, 0, c);
2241 insertSourceImage(tname, flags_image);
2242 flags_image->drop();
2243 return getTexture(tname);
2247 std::vector<std::string> getTextureDirs()
2249 return fs::GetRecursiveDirs(g_settings->get("texture_path"));