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;
434 IWritableTextureSource *createTextureSource()
436 return new TextureSource();
439 TextureSource::TextureSource()
441 m_main_thread = std::this_thread::get_id();
443 // Add a NULL TextureInfo as the first index, named ""
444 m_textureinfo_cache.emplace_back("");
445 m_name_to_id[""] = 0;
447 // Cache some settings
448 // Note: Since this is only done once, the game must be restarted
449 // for these settings to take effect
450 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
451 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
454 TextureSource::~TextureSource()
456 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
458 unsigned int textures_before = driver->getTextureCount();
460 for (const auto &iter : m_textureinfo_cache) {
463 driver->removeTexture(iter.texture);
465 m_textureinfo_cache.clear();
467 for (auto t : m_texture_trash) {
468 //cleanup trashed texture
469 driver->removeTexture(t);
472 infostream << "~TextureSource() before cleanup: "<< textures_before
473 << " after: " << driver->getTextureCount() << std::endl;
476 u32 TextureSource::getTextureId(const std::string &name)
478 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
482 See if texture already exists
484 MutexAutoLock lock(m_textureinfo_cache_mutex);
485 std::map<std::string, u32>::iterator n;
486 n = m_name_to_id.find(name);
487 if (n != m_name_to_id.end())
496 if (std::this_thread::get_id() == m_main_thread) {
497 return generateTexture(name);
501 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
503 // We're gonna ask the result to be put into here
504 static ResultQueue<std::string, u32, u8, u8> result_queue;
506 // Throw a request in
507 m_get_texture_queue.add(name, 0, 0, &result_queue);
511 // Wait result for a second
512 GetResult<std::string, u32, u8, u8>
513 result = result_queue.pop_front(1000);
515 if (result.key == name) {
519 } catch(ItemNotFoundException &e) {
520 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
524 infostream << "getTextureId(): Failed" << std::endl;
529 // Draw an image on top of an another one, using the alpha channel of the
531 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
532 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
534 // Like blit_with_alpha, but only modifies destination pixels that
536 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
537 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
539 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
540 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
541 // color alpha with the destination alpha.
542 // Otherwise, any pixels that are not fully transparent get the color alpha.
543 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
544 const video::SColor &color, int ratio, bool keep_alpha);
546 // paint a texture using the given color
547 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
548 const video::SColor &color);
550 // Apply a mask to an image
551 static void apply_mask(video::IImage *mask, video::IImage *dst,
552 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
554 // Draw or overlay a crack
555 static void draw_crack(video::IImage *crack, video::IImage *dst,
556 bool use_overlay, s32 frame_count, s32 progression,
557 video::IVideoDriver *driver, u8 tiles = 1);
560 void brighten(video::IImage *image);
561 // Parse a transform name
562 u32 parseImageTransform(const std::string& s);
563 // Apply transform to image dimension
564 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
565 // Apply transform to image data
566 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
569 This method generates all the textures
571 u32 TextureSource::generateTexture(const std::string &name)
573 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
575 // Empty name means texture 0
577 infostream<<"generateTexture(): name is empty"<<std::endl;
583 See if texture already exists
585 MutexAutoLock lock(m_textureinfo_cache_mutex);
586 std::map<std::string, u32>::iterator n;
587 n = m_name_to_id.find(name);
588 if (n != m_name_to_id.end()) {
594 Calling only allowed from main thread
596 if (std::this_thread::get_id() != m_main_thread) {
597 errorstream<<"TextureSource::generateTexture() "
598 "called not from main thread"<<std::endl;
602 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
603 sanity_check(driver);
605 video::IImage *img = generateImage(name);
607 video::ITexture *tex = NULL;
611 img = Align2Npot2(img, driver);
613 // Create texture from resulting image
614 tex = driver->addTexture(name.c_str(), img);
615 guiScalingCache(io::path(name.c_str()), driver, img);
620 Add texture to caches (add NULL textures too)
623 MutexAutoLock lock(m_textureinfo_cache_mutex);
625 u32 id = m_textureinfo_cache.size();
626 TextureInfo ti(name, tex);
627 m_textureinfo_cache.push_back(ti);
628 m_name_to_id[name] = id;
633 std::string TextureSource::getTextureName(u32 id)
635 MutexAutoLock lock(m_textureinfo_cache_mutex);
637 if (id >= m_textureinfo_cache.size())
639 errorstream<<"TextureSource::getTextureName(): id="<<id
640 <<" >= m_textureinfo_cache.size()="
641 <<m_textureinfo_cache.size()<<std::endl;
645 return m_textureinfo_cache[id].name;
648 video::ITexture* TextureSource::getTexture(u32 id)
650 MutexAutoLock lock(m_textureinfo_cache_mutex);
652 if (id >= m_textureinfo_cache.size())
655 return m_textureinfo_cache[id].texture;
658 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
660 u32 actual_id = getTextureId(name);
664 return getTexture(actual_id);
667 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
669 static thread_local bool filter_needed =
670 g_settings->getBool("texture_clean_transparent") ||
671 ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
672 g_settings->getS32("texture_min_size") > 1);
673 // Avoid duplicating texture if it won't actually change
675 return getTexture(name + "^[applyfiltersformesh", id);
676 return getTexture(name, id);
679 Palette* TextureSource::getPalette(const std::string &name)
681 // Only the main thread may load images
682 sanity_check(std::this_thread::get_id() == m_main_thread);
687 auto it = m_palettes.find(name);
688 if (it == m_palettes.end()) {
690 video::IImage *img = generateImage(name);
692 warningstream << "TextureSource::getPalette(): palette \"" << name
693 << "\" could not be loaded." << std::endl;
697 u32 w = img->getDimension().Width;
698 u32 h = img->getDimension().Height;
699 // Real area of the image
704 warningstream << "TextureSource::getPalette(): the specified"
705 << " palette image \"" << name << "\" is larger than 256"
706 << " pixels, using the first 256." << std::endl;
708 } else if (256 % area != 0)
709 warningstream << "TextureSource::getPalette(): the "
710 << "specified palette image \"" << name << "\" does not "
711 << "contain power of two pixels." << std::endl;
712 // We stretch the palette so it will fit 256 values
713 // This many param2 values will have the same color
714 u32 step = 256 / area;
715 // For each pixel in the image
716 for (u32 i = 0; i < area; i++) {
717 video::SColor c = img->getPixel(i % w, i / w);
718 // Fill in palette with 'step' colors
719 for (u32 j = 0; j < step; j++)
720 new_palette.push_back(c);
723 // Fill in remaining elements
724 while (new_palette.size() < 256)
725 new_palette.emplace_back(0xFFFFFFFF);
726 m_palettes[name] = new_palette;
727 it = m_palettes.find(name);
729 if (it != m_palettes.end())
730 return &((*it).second);
734 void TextureSource::processQueue()
739 //NOTE this is only thread safe for ONE consumer thread!
740 if (!m_get_texture_queue.empty())
742 GetRequest<std::string, u32, u8, u8>
743 request = m_get_texture_queue.pop();
745 /*infostream<<"TextureSource::processQueue(): "
746 <<"got texture request with "
747 <<"name=\""<<request.key<<"\""
750 m_get_texture_queue.pushResult(request, generateTexture(request.key));
754 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
756 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
758 sanity_check(std::this_thread::get_id() == m_main_thread);
760 m_sourcecache.insert(name, img, true);
761 m_source_image_existence.set(name, true);
764 void TextureSource::rebuildImagesAndTextures()
766 MutexAutoLock lock(m_textureinfo_cache_mutex);
768 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
769 sanity_check(driver);
771 infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
772 << " textures" << std::endl;
775 for (TextureInfo &ti : m_textureinfo_cache) {
776 video::IImage *img = generateImage(ti.name);
778 img = Align2Npot2(img, driver);
780 // Create texture from resulting image
781 video::ITexture *t = NULL;
783 t = driver->addTexture(ti.name.c_str(), img);
784 guiScalingCache(io::path(ti.name.c_str()), driver, img);
787 video::ITexture *t_old = ti.texture;
792 m_texture_trash.push_back(t_old);
796 inline static void applyShadeFactor(video::SColor &color, u32 factor)
798 u32 f = core::clamp<u32>(factor, 0, 256);
799 color.setRed(color.getRed() * f / 256);
800 color.setGreen(color.getGreen() * f / 256);
801 color.setBlue(color.getBlue() * f / 256);
804 static video::IImage *createInventoryCubeImage(
805 video::IImage *top, video::IImage *left, video::IImage *right)
807 core::dimension2du size_top = top->getDimension();
808 core::dimension2du size_left = left->getDimension();
809 core::dimension2du size_right = right->getDimension();
811 u32 size = npot2(std::max({
812 size_top.Width, size_top.Height,
813 size_left.Width, size_left.Height,
814 size_right.Width, size_right.Height,
817 // It must be divisible by 4, to let everything work correctly.
818 // But it is a power of 2, so being at least 4 is the same.
819 // And the resulting texture should't be too large as well.
820 size = core::clamp<u32>(size, 4, 64);
822 // With such parameters, the cube fits exactly, touching each image line
823 // from `0` to `cube_size - 1`. (Note that division is exact here).
824 u32 cube_size = 9 * size;
825 u32 offset = size / 2;
827 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
829 auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
831 core::dimension2du dim = image->getDimension();
832 video::ECOLOR_FORMAT format = image->getColorFormat();
833 if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
834 video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
835 image->copyToScaling(scaled);
839 sanity_check(image->getPitch() == 4 * size);
840 return reinterpret_cast<u32 *>(image->lock());
842 auto free_image = [] (video::IImage *image) -> void {
847 video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
848 sanity_check(result->getPitch() == 4 * cube_size);
849 result->fill(video::SColor(0x00000000u));
850 u32 *target = reinterpret_cast<u32 *>(result->lock());
852 // Draws single cube face
853 // `shade_factor` is face brightness, in range [0.0, 1.0]
854 // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
855 // `offsets` list pixels to be drawn for single source pixel
856 auto draw_image = [=] (video::IImage *image, float shade_factor,
857 s16 xu, s16 xv, s16 x1,
858 s16 yu, s16 yv, s16 y1,
859 std::initializer_list<v2s16> offsets) -> void {
860 u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
861 const u32 *source = lock_image(image);
862 for (u16 v = 0; v < size; v++) {
863 for (u16 u = 0; u < size; u++) {
864 video::SColor pixel(*source);
865 applyShadeFactor(pixel, brightness);
866 s16 x = xu * u + xv * v + x1;
867 s16 y = yu * u + yv * v + y1;
868 for (const auto &off : offsets)
869 target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
876 draw_image(top, 1.000000f,
877 4, -4, 4 * (size - 1),
880 {2, 0}, {3, 0}, {4, 0}, {5, 0},
881 {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
882 {2, 2}, {3, 2}, {4, 2}, {5, 2},
885 draw_image(left, 0.836660f,
890 {0, 1}, {1, 1}, {2, 1}, {3, 1},
891 {0, 2}, {1, 2}, {2, 2}, {3, 2},
892 {0, 3}, {1, 3}, {2, 3}, {3, 3},
893 {0, 4}, {1, 4}, {2, 4}, {3, 4},
897 draw_image(right, 0.670820f,
902 {0, 1}, {1, 1}, {2, 1}, {3, 1},
903 {0, 2}, {1, 2}, {2, 2}, {3, 2},
904 {0, 3}, {1, 3}, {2, 3}, {3, 3},
905 {0, 4}, {1, 4}, {2, 4}, {3, 4},
913 video::IImage* TextureSource::generateImage(const std::string &name)
915 // Get the base image
917 const char separator = '^';
918 const char escape = '\\';
919 const char paren_open = '(';
920 const char paren_close = ')';
922 // Find last separator in the name
923 s32 last_separator_pos = -1;
925 for (s32 i = name.size() - 1; i >= 0; i--) {
926 if (i > 0 && name[i-1] == escape)
930 if (paren_bal == 0) {
931 last_separator_pos = i;
932 i = -1; // break out of loop
936 if (paren_bal == 0) {
937 errorstream << "generateImage(): unbalanced parentheses"
938 << "(extranous '(') while generating texture \""
939 << name << "\"" << std::endl;
952 errorstream << "generateImage(): unbalanced parentheses"
953 << "(missing matching '(') while generating texture \""
954 << name << "\"" << std::endl;
959 video::IImage *baseimg = NULL;
962 If separator was found, make the base image
963 using a recursive call.
965 if (last_separator_pos != -1) {
966 baseimg = generateImage(name.substr(0, last_separator_pos));
970 Parse out the last part of the name of the image and act
974 std::string last_part_of_name = name.substr(last_separator_pos + 1);
977 If this name is enclosed in parentheses, generate it
978 and blit it onto the base image
980 if (last_part_of_name[0] == paren_open
981 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
982 std::string name2 = last_part_of_name.substr(1,
983 last_part_of_name.size() - 2);
984 video::IImage *tmp = generateImage(name2);
986 errorstream << "generateImage(): "
987 "Failed to generate \"" << name2 << "\""
991 core::dimension2d<u32> dim = tmp->getDimension();
993 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
998 } else if (!generateImagePart(last_part_of_name, baseimg)) {
999 // Generate image according to part of name
1000 errorstream << "generateImage(): "
1001 "Failed to generate \"" << last_part_of_name << "\""
1005 // If no resulting image, print a warning
1006 if (baseimg == NULL) {
1007 errorstream << "generateImage(): baseimg is NULL (attempted to"
1008 " create texture \"" << name << "\")" << std::endl;
1017 static inline u16 get_GL_major_version()
1019 const GLubyte *gl_version = glGetString(GL_VERSION);
1020 return (u16) (gl_version[0] - '0');
1024 * Check if hardware requires npot2 aligned textures
1025 * @return true if alignment NOT(!) requires, false otherwise
1028 bool hasNPotSupport()
1030 // Only GLES2 is trusted to correctly report npot support
1031 // Note: we cache the boolean result, the GL context will never change.
1032 static const bool supported = get_GL_major_version() > 1 &&
1033 glGetString(GL_EXTENSIONS) &&
1034 strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
1039 * Check and align image to npot2 if required by hardware
1040 * @param image image to check for npot2 alignment
1041 * @param driver driver to use for image operations
1042 * @return image or copy of image aligned to npot2
1045 video::IImage * Align2Npot2(video::IImage * image,
1046 video::IVideoDriver* driver)
1051 if (hasNPotSupport())
1054 core::dimension2d<u32> dim = image->getDimension();
1055 unsigned int height = npot2(dim.Height);
1056 unsigned int width = npot2(dim.Width);
1058 if (dim.Height == height && dim.Width == width)
1061 if (dim.Height > height)
1063 if (dim.Width > width)
1066 video::IImage *targetimage =
1067 driver->createImage(video::ECF_A8R8G8B8,
1068 core::dimension2d<u32>(width, height));
1070 if (targetimage != NULL)
1071 image->copyToScaling(targetimage);
1078 static std::string unescape_string(const std::string &str, const char esc = '\\')
1081 size_t pos = 0, cpos;
1082 out.reserve(str.size());
1084 cpos = str.find_first_of(esc, pos);
1085 if (cpos == std::string::npos) {
1086 out += str.substr(pos);
1089 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1095 bool TextureSource::generateImagePart(std::string part_of_name,
1096 video::IImage *& baseimg)
1098 const char escape = '\\'; // same as in generateImage()
1099 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1100 sanity_check(driver);
1102 // Stuff starting with [ are special commands
1103 if (part_of_name.empty() || part_of_name[0] != '[') {
1104 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1106 image = Align2Npot2(image, driver);
1108 if (image == NULL) {
1109 if (!part_of_name.empty()) {
1111 // Do not create normalmap dummies
1112 if (part_of_name.find("_normal.png") != std::string::npos) {
1113 warningstream << "generateImage(): Could not load normal map \""
1114 << part_of_name << "\"" << std::endl;
1118 errorstream << "generateImage(): Could not load image \""
1119 << part_of_name << "\" while building texture; "
1120 "Creating a dummy image" << std::endl;
1123 // Just create a dummy image
1124 //core::dimension2d<u32> dim(2,2);
1125 core::dimension2d<u32> dim(1,1);
1126 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1127 sanity_check(image != NULL);
1128 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1129 image->setPixel(1,0, video::SColor(255,0,255,0));
1130 image->setPixel(0,1, video::SColor(255,0,0,255));
1131 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1132 image->setPixel(0,0, video::SColor(255,myrand()%256,
1133 myrand()%256,myrand()%256));
1134 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1135 myrand()%256,myrand()%256));
1136 image->setPixel(0,1, video::SColor(255,myrand()%256,
1137 myrand()%256,myrand()%256));
1138 image->setPixel(1,1, video::SColor(255,myrand()%256,
1139 myrand()%256,myrand()%256));*/
1142 // If base image is NULL, load as base.
1143 if (baseimg == NULL)
1145 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1147 Copy it this way to get an alpha channel.
1148 Otherwise images with alpha cannot be blitted on
1149 images that don't have alpha in the original file.
1151 core::dimension2d<u32> dim = image->getDimension();
1152 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1153 image->copyTo(baseimg);
1155 // Else blit on base.
1158 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1159 // Size of the copied area
1160 core::dimension2d<u32> dim = image->getDimension();
1161 //core::dimension2d<u32> dim(16,16);
1162 // Position to copy the blitted to in the base image
1163 core::position2d<s32> pos_to(0,0);
1164 // Position to copy the blitted from in the blitted image
1165 core::position2d<s32> pos_from(0,0);
1167 /*image->copyToWithAlpha(baseimg, pos_to,
1168 core::rect<s32>(pos_from, dim),
1169 video::SColor(255,255,255,255),
1172 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1173 if (dim == dim_dst) {
1174 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1175 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1176 // Upscale overlying image
1177 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1178 createImage(video::ECF_A8R8G8B8, dim_dst);
1179 image->copyToScaling(scaled_image);
1181 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1182 scaled_image->drop();
1184 // Upscale base image
1185 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1186 createImage(video::ECF_A8R8G8B8, dim);
1187 baseimg->copyToScaling(scaled_base);
1189 baseimg = scaled_base;
1191 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1199 // A special texture modification
1201 /*infostream<<"generateImage(): generating special "
1202 <<"modification \""<<part_of_name<<"\""
1208 Adds a cracking texture
1209 N = animation frame count, P = crack progression
1211 if (str_starts_with(part_of_name, "[crack"))
1213 if (baseimg == NULL) {
1214 errorstream<<"generateImagePart(): baseimg == NULL "
1215 <<"for part_of_name=\""<<part_of_name
1216 <<"\", cancelling."<<std::endl;
1220 // Crack image number and overlay option
1221 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1222 bool use_overlay = (part_of_name[6] == 'o');
1223 Strfnd sf(part_of_name);
1225 s32 frame_count = stoi(sf.next(":"));
1226 s32 progression = stoi(sf.next(":"));
1228 // Check whether there is the <tiles> argument, that is,
1229 // whether there are 3 arguments. If so, shift values
1230 // as the first and not the last argument is optional.
1231 auto s = sf.next(":");
1233 tiles = frame_count;
1234 frame_count = progression;
1235 progression = stoi(s);
1238 if (progression >= 0) {
1242 It is an image with a number of cracking stages
1245 video::IImage *img_crack = m_sourcecache.getOrLoad(
1246 "crack_anylength.png");
1249 draw_crack(img_crack, baseimg,
1250 use_overlay, frame_count,
1251 progression, driver, tiles);
1257 [combine:WxH:X,Y=filename:X,Y=filename2
1258 Creates a bigger texture from any amount of smaller ones
1260 else if (str_starts_with(part_of_name, "[combine"))
1262 Strfnd sf(part_of_name);
1264 u32 w0 = stoi(sf.next("x"));
1265 u32 h0 = stoi(sf.next(":"));
1266 core::dimension2d<u32> dim(w0,h0);
1267 if (baseimg == NULL) {
1268 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1269 baseimg->fill(video::SColor(0,0,0,0));
1271 while (!sf.at_end()) {
1272 u32 x = stoi(sf.next(","));
1273 u32 y = stoi(sf.next("="));
1274 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1275 infostream<<"Adding \""<<filename
1276 <<"\" to combined ("<<x<<","<<y<<")"
1278 video::IImage *img = generateImage(filename);
1280 core::dimension2d<u32> dim = img->getDimension();
1281 core::position2d<s32> pos_base(x, y);
1282 video::IImage *img2 =
1283 driver->createImage(video::ECF_A8R8G8B8, dim);
1286 /*img2->copyToWithAlpha(baseimg, pos_base,
1287 core::rect<s32>(v2s32(0,0), dim),
1288 video::SColor(255,255,255,255),
1290 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1293 errorstream << "generateImagePart(): Failed to load image \""
1294 << filename << "\" for [combine" << std::endl;
1301 else if (str_starts_with(part_of_name, "[brighten"))
1303 if (baseimg == NULL) {
1304 errorstream<<"generateImagePart(): baseimg==NULL "
1305 <<"for part_of_name=\""<<part_of_name
1306 <<"\", cancelling."<<std::endl;
1314 Make image completely opaque.
1315 Used for the leaves texture when in old leaves mode, so
1316 that the transparent parts don't look completely black
1317 when simple alpha channel is used for rendering.
1319 else if (str_starts_with(part_of_name, "[noalpha"))
1321 if (baseimg == NULL){
1322 errorstream<<"generateImagePart(): baseimg==NULL "
1323 <<"for part_of_name=\""<<part_of_name
1324 <<"\", cancelling."<<std::endl;
1328 core::dimension2d<u32> dim = baseimg->getDimension();
1330 // Set alpha to full
1331 for (u32 y=0; y<dim.Height; y++)
1332 for (u32 x=0; x<dim.Width; x++)
1334 video::SColor c = baseimg->getPixel(x,y);
1336 baseimg->setPixel(x,y,c);
1341 Convert one color to transparent.
1343 else if (str_starts_with(part_of_name, "[makealpha:"))
1345 if (baseimg == NULL) {
1346 errorstream<<"generateImagePart(): baseimg == NULL "
1347 <<"for part_of_name=\""<<part_of_name
1348 <<"\", cancelling."<<std::endl;
1352 Strfnd sf(part_of_name.substr(11));
1353 u32 r1 = stoi(sf.next(","));
1354 u32 g1 = stoi(sf.next(","));
1355 u32 b1 = stoi(sf.next(""));
1357 core::dimension2d<u32> dim = baseimg->getDimension();
1359 /*video::IImage *oldbaseimg = baseimg;
1360 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1361 oldbaseimg->copyTo(baseimg);
1362 oldbaseimg->drop();*/
1364 // Set alpha to full
1365 for (u32 y=0; y<dim.Height; y++)
1366 for (u32 x=0; x<dim.Width; x++)
1368 video::SColor c = baseimg->getPixel(x,y);
1370 u32 g = c.getGreen();
1371 u32 b = c.getBlue();
1372 if (!(r == r1 && g == g1 && b == b1))
1375 baseimg->setPixel(x,y,c);
1380 Rotates and/or flips the image.
1382 N can be a number (between 0 and 7) or a transform name.
1383 Rotations are counter-clockwise.
1385 1 R90 rotate by 90 degrees
1386 2 R180 rotate by 180 degrees
1387 3 R270 rotate by 270 degrees
1389 5 FXR90 flip X then rotate by 90 degrees
1391 7 FYR90 flip Y then rotate by 90 degrees
1393 Note: Transform names can be concatenated to produce
1394 their product (applies the first then the second).
1395 The resulting transform will be equivalent to one of the
1396 eight existing ones, though (see: dihedral group).
1398 else if (str_starts_with(part_of_name, "[transform"))
1400 if (baseimg == NULL) {
1401 errorstream<<"generateImagePart(): baseimg == NULL "
1402 <<"for part_of_name=\""<<part_of_name
1403 <<"\", cancelling."<<std::endl;
1407 u32 transform = parseImageTransform(part_of_name.substr(10));
1408 core::dimension2d<u32> dim = imageTransformDimension(
1409 transform, baseimg->getDimension());
1410 video::IImage *image = driver->createImage(
1411 baseimg->getColorFormat(), dim);
1412 sanity_check(image != NULL);
1413 imageTransform(transform, baseimg, image);
1418 [inventorycube{topimage{leftimage{rightimage
1419 In every subimage, replace ^ with &.
1420 Create an "inventory cube".
1421 NOTE: This should be used only on its own.
1422 Example (a grass block (not actually used in game):
1423 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1425 else if (str_starts_with(part_of_name, "[inventorycube"))
1427 if (baseimg != NULL){
1428 errorstream<<"generateImagePart(): baseimg != NULL "
1429 <<"for part_of_name=\""<<part_of_name
1430 <<"\", cancelling."<<std::endl;
1434 str_replace(part_of_name, '&', '^');
1435 Strfnd sf(part_of_name);
1437 std::string imagename_top = sf.next("{");
1438 std::string imagename_left = sf.next("{");
1439 std::string imagename_right = sf.next("{");
1441 // Generate images for the faces of the cube
1442 video::IImage *img_top = generateImage(imagename_top);
1443 video::IImage *img_left = generateImage(imagename_left);
1444 video::IImage *img_right = generateImage(imagename_right);
1446 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1447 errorstream << "generateImagePart(): Failed to create textures"
1448 << " for inventorycube \"" << part_of_name << "\""
1450 baseimg = generateImage(imagename_top);
1454 baseimg = createInventoryCubeImage(img_top, img_left, img_right);
1456 // Face images are not needed anymore
1464 [lowpart:percent:filename
1465 Adds the lower part of a texture
1467 else if (str_starts_with(part_of_name, "[lowpart:"))
1469 Strfnd sf(part_of_name);
1471 u32 percent = stoi(sf.next(":"));
1472 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1474 if (baseimg == NULL)
1475 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1476 video::IImage *img = generateImage(filename);
1479 core::dimension2d<u32> dim = img->getDimension();
1480 core::position2d<s32> pos_base(0, 0);
1481 video::IImage *img2 =
1482 driver->createImage(video::ECF_A8R8G8B8, dim);
1485 core::position2d<s32> clippos(0, 0);
1486 clippos.Y = dim.Height * (100-percent) / 100;
1487 core::dimension2d<u32> clipdim = dim;
1488 clipdim.Height = clipdim.Height * percent / 100 + 1;
1489 core::rect<s32> cliprect(clippos, clipdim);
1490 img2->copyToWithAlpha(baseimg, pos_base,
1491 core::rect<s32>(v2s32(0,0), dim),
1492 video::SColor(255,255,255,255),
1499 Crops a frame of a vertical animation.
1500 N = frame count, I = frame index
1502 else if (str_starts_with(part_of_name, "[verticalframe:"))
1504 Strfnd sf(part_of_name);
1506 u32 frame_count = stoi(sf.next(":"));
1507 u32 frame_index = stoi(sf.next(":"));
1509 if (baseimg == NULL){
1510 errorstream<<"generateImagePart(): baseimg != NULL "
1511 <<"for part_of_name=\""<<part_of_name
1512 <<"\", cancelling."<<std::endl;
1516 v2u32 frame_size = baseimg->getDimension();
1517 frame_size.Y /= frame_count;
1519 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1522 errorstream<<"generateImagePart(): Could not create image "
1523 <<"for part_of_name=\""<<part_of_name
1524 <<"\", cancelling."<<std::endl;
1528 // Fill target image with transparency
1529 img->fill(video::SColor(0,0,0,0));
1531 core::dimension2d<u32> dim = frame_size;
1532 core::position2d<s32> pos_dst(0, 0);
1533 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1534 baseimg->copyToWithAlpha(img, pos_dst,
1535 core::rect<s32>(pos_src, dim),
1536 video::SColor(255,255,255,255),
1544 Applies a mask to an image
1546 else if (str_starts_with(part_of_name, "[mask:"))
1548 if (baseimg == NULL) {
1549 errorstream << "generateImage(): baseimg == NULL "
1550 << "for part_of_name=\"" << part_of_name
1551 << "\", cancelling." << std::endl;
1554 Strfnd sf(part_of_name);
1556 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1558 video::IImage *img = generateImage(filename);
1560 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1561 img->getDimension());
1564 errorstream << "generateImage(): Failed to load \""
1565 << filename << "\".";
1570 multiplys a given color to any pixel of an image
1571 color = color as ColorString
1573 else if (str_starts_with(part_of_name, "[multiply:")) {
1574 Strfnd sf(part_of_name);
1576 std::string color_str = sf.next(":");
1578 if (baseimg == NULL) {
1579 errorstream << "generateImagePart(): baseimg != NULL "
1580 << "for part_of_name=\"" << part_of_name
1581 << "\", cancelling." << std::endl;
1585 video::SColor color;
1587 if (!parseColorString(color_str, color, false))
1590 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1594 Overlays image with given color
1595 color = color as ColorString
1597 else if (str_starts_with(part_of_name, "[colorize:"))
1599 Strfnd sf(part_of_name);
1601 std::string color_str = sf.next(":");
1602 std::string ratio_str = sf.next(":");
1604 if (baseimg == NULL) {
1605 errorstream << "generateImagePart(): baseimg != NULL "
1606 << "for part_of_name=\"" << part_of_name
1607 << "\", cancelling." << std::endl;
1611 video::SColor color;
1613 bool keep_alpha = false;
1615 if (!parseColorString(color_str, color, false))
1618 if (is_number(ratio_str))
1619 ratio = mystoi(ratio_str, 0, 255);
1620 else if (ratio_str == "alpha")
1623 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1626 [applyfiltersformesh
1629 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1631 /* IMPORTANT: When changing this, getTextureForMesh() needs to be
1635 errorstream << "generateImagePart(): baseimg == NULL "
1636 << "for part_of_name=\"" << part_of_name
1637 << "\", cancelling." << std::endl;
1641 // Apply the "clean transparent" filter, if configured.
1642 if (g_settings->getBool("texture_clean_transparent"))
1643 imageCleanTransparent(baseimg, 127);
1645 /* Upscale textures to user's requested minimum size. This is a trick to make
1646 * filters look as good on low-res textures as on high-res ones, by making
1647 * low-res textures BECOME high-res ones. This is helpful for worlds that
1648 * mix high- and low-res textures, or for mods with least-common-denominator
1649 * textures that don't have the resources to offer high-res alternatives.
1651 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1652 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1654 const core::dimension2d<u32> dim = baseimg->getDimension();
1656 /* Calculate scaling needed to make the shortest texture dimension
1657 * equal to the target minimum. If e.g. this is a vertical frames
1658 * animation, the short dimension will be the real size.
1660 if ((dim.Width == 0) || (dim.Height == 0)) {
1661 errorstream << "generateImagePart(): Illegal 0 dimension "
1662 << "for part_of_name=\""<< part_of_name
1663 << "\", cancelling." << std::endl;
1666 u32 xscale = scaleto / dim.Width;
1667 u32 yscale = scaleto / dim.Height;
1668 u32 scale = (xscale > yscale) ? xscale : yscale;
1670 // Never downscale; only scale up by 2x or more.
1672 u32 w = scale * dim.Width;
1673 u32 h = scale * dim.Height;
1674 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1675 video::IImage *newimg = driver->createImage(
1676 baseimg->getColorFormat(), newdim);
1677 baseimg->copyToScaling(newimg);
1685 Resizes the base image to the given dimensions
1687 else if (str_starts_with(part_of_name, "[resize"))
1689 if (baseimg == NULL) {
1690 errorstream << "generateImagePart(): baseimg == NULL "
1691 << "for part_of_name=\""<< part_of_name
1692 << "\", cancelling." << std::endl;
1696 Strfnd sf(part_of_name);
1698 u32 width = stoi(sf.next("x"));
1699 u32 height = stoi(sf.next(""));
1700 core::dimension2d<u32> dim(width, height);
1702 video::IImage *image = RenderingEngine::get_video_driver()->
1703 createImage(video::ECF_A8R8G8B8, dim);
1704 baseimg->copyToScaling(image);
1710 Makes the base image transparent according to the given ratio.
1711 R must be between 0 and 255.
1712 0 means totally transparent.
1713 255 means totally opaque.
1715 else if (str_starts_with(part_of_name, "[opacity:")) {
1716 if (baseimg == NULL) {
1717 errorstream << "generateImagePart(): baseimg == NULL "
1718 << "for part_of_name=\"" << part_of_name
1719 << "\", cancelling." << std::endl;
1723 Strfnd sf(part_of_name);
1726 u32 ratio = mystoi(sf.next(""), 0, 255);
1728 core::dimension2d<u32> dim = baseimg->getDimension();
1730 for (u32 y = 0; y < dim.Height; y++)
1731 for (u32 x = 0; x < dim.Width; x++)
1733 video::SColor c = baseimg->getPixel(x, y);
1734 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1735 baseimg->setPixel(x, y, c);
1740 Inverts the given channels of the base image.
1741 Mode may contain the characters "r", "g", "b", "a".
1742 Only the channels that are mentioned in the mode string
1745 else if (str_starts_with(part_of_name, "[invert:")) {
1746 if (baseimg == NULL) {
1747 errorstream << "generateImagePart(): baseimg == NULL "
1748 << "for part_of_name=\"" << part_of_name
1749 << "\", cancelling." << std::endl;
1753 Strfnd sf(part_of_name);
1756 std::string mode = sf.next("");
1758 if (mode.find('a') != std::string::npos)
1759 mask |= 0xff000000UL;
1760 if (mode.find('r') != std::string::npos)
1761 mask |= 0x00ff0000UL;
1762 if (mode.find('g') != std::string::npos)
1763 mask |= 0x0000ff00UL;
1764 if (mode.find('b') != std::string::npos)
1765 mask |= 0x000000ffUL;
1767 core::dimension2d<u32> dim = baseimg->getDimension();
1769 for (u32 y = 0; y < dim.Height; y++)
1770 for (u32 x = 0; x < dim.Width; x++)
1772 video::SColor c = baseimg->getPixel(x, y);
1774 baseimg->setPixel(x, y, c);
1779 Retrieves a tile at position X,Y (in tiles)
1780 from the base image it assumes to be a
1781 tilesheet with dimensions W,H (in tiles).
1783 else if (part_of_name.substr(0,7) == "[sheet:") {
1784 if (baseimg == NULL) {
1785 errorstream << "generateImagePart(): baseimg != NULL "
1786 << "for part_of_name=\"" << part_of_name
1787 << "\", cancelling." << std::endl;
1791 Strfnd sf(part_of_name);
1793 u32 w0 = stoi(sf.next("x"));
1794 u32 h0 = stoi(sf.next(":"));
1795 u32 x0 = stoi(sf.next(","));
1796 u32 y0 = stoi(sf.next(":"));
1798 core::dimension2d<u32> img_dim = baseimg->getDimension();
1799 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1801 video::IImage *img = driver->createImage(
1802 video::ECF_A8R8G8B8, tile_dim);
1804 errorstream << "generateImagePart(): Could not create image "
1805 << "for part_of_name=\"" << part_of_name
1806 << "\", cancelling." << std::endl;
1810 img->fill(video::SColor(0,0,0,0));
1811 v2u32 vdim(tile_dim);
1812 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1813 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1814 video::SColor(255,255,255,255), NULL);
1822 errorstream << "generateImagePart(): Invalid "
1823 " modification: \"" << part_of_name << "\"" << std::endl;
1831 Calculate the color of a single pixel drawn on top of another pixel.
1833 This is a little more complicated than just video::SColor::getInterpolated
1834 because getInterpolated does not handle alpha correctly. For example, a
1835 pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
1836 pixel with alpha=160, while getInterpolated would yield alpha=96.
1838 static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio)
1840 if (dst_c.getAlpha() == 0)
1842 video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f);
1843 out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) *
1844 src_c.getAlpha() * ratio / (255 * 255));
1849 Draw an image on top of an another one, using the alpha channel of the
1852 This exists because IImage::copyToWithAlpha() doesn't seem to always
1855 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1856 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1858 for (u32 y0=0; y0<size.Y; y0++)
1859 for (u32 x0=0; x0<size.X; x0++)
1861 s32 src_x = src_pos.X + x0;
1862 s32 src_y = src_pos.Y + y0;
1863 s32 dst_x = dst_pos.X + x0;
1864 s32 dst_y = dst_pos.Y + y0;
1865 video::SColor src_c = src->getPixel(src_x, src_y);
1866 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1867 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1868 dst->setPixel(dst_x, dst_y, dst_c);
1873 Draw an image on top of an another one, using the alpha channel of the
1874 source image; only modify fully opaque pixels in destinaion
1876 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1877 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1879 for (u32 y0=0; y0<size.Y; y0++)
1880 for (u32 x0=0; x0<size.X; x0++)
1882 s32 src_x = src_pos.X + x0;
1883 s32 src_y = src_pos.Y + y0;
1884 s32 dst_x = dst_pos.X + x0;
1885 s32 dst_y = dst_pos.Y + y0;
1886 video::SColor src_c = src->getPixel(src_x, src_y);
1887 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1888 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1890 dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
1891 dst->setPixel(dst_x, dst_y, dst_c);
1896 // This function has been disabled because it is currently unused.
1897 // Feel free to re-enable if you find it handy.
1900 Draw an image on top of an another one, using the specified ratio
1901 modify all partially-opaque pixels in the destination.
1903 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1904 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1906 for (u32 y0 = 0; y0 < size.Y; y0++)
1907 for (u32 x0 = 0; x0 < size.X; x0++)
1909 s32 src_x = src_pos.X + x0;
1910 s32 src_y = src_pos.Y + y0;
1911 s32 dst_x = dst_pos.X + x0;
1912 s32 dst_y = dst_pos.Y + y0;
1913 video::SColor src_c = src->getPixel(src_x, src_y);
1914 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1915 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1918 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1920 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1921 dst->setPixel(dst_x, dst_y, dst_c);
1928 Apply color to destination
1930 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1931 const video::SColor &color, int ratio, bool keep_alpha)
1933 u32 alpha = color.getAlpha();
1934 video::SColor dst_c;
1935 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1936 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1938 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1939 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1940 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1941 if (dst_alpha > 0) {
1942 dst_c.setAlpha(dst_alpha * alpha / 255);
1943 dst->setPixel(x, y, dst_c);
1946 } else { // replace the color including the alpha
1947 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1948 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1949 if (dst->getPixel(x, y).getAlpha() > 0)
1950 dst->setPixel(x, y, color);
1952 } else { // interpolate between the color and destination
1953 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1954 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1955 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1956 dst_c = dst->getPixel(x, y);
1957 if (dst_c.getAlpha() > 0) {
1958 dst_c = color.getInterpolated(dst_c, interp);
1959 dst->setPixel(x, y, dst_c);
1966 Apply color to destination
1968 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1969 const video::SColor &color)
1971 video::SColor dst_c;
1973 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1974 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1975 dst_c = dst->getPixel(x, y);
1978 (dst_c.getRed() * color.getRed()) / 255,
1979 (dst_c.getGreen() * color.getGreen()) / 255,
1980 (dst_c.getBlue() * color.getBlue()) / 255
1982 dst->setPixel(x, y, dst_c);
1987 Apply mask to destination
1989 static void apply_mask(video::IImage *mask, video::IImage *dst,
1990 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1992 for (u32 y0 = 0; y0 < size.Y; y0++) {
1993 for (u32 x0 = 0; x0 < size.X; x0++) {
1994 s32 mask_x = x0 + mask_pos.X;
1995 s32 mask_y = y0 + mask_pos.Y;
1996 s32 dst_x = x0 + dst_pos.X;
1997 s32 dst_y = y0 + dst_pos.Y;
1998 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1999 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2000 dst_c.color &= mask_c.color;
2001 dst->setPixel(dst_x, dst_y, dst_c);
2006 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2007 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2009 core::dimension2d<u32> strip_size = crack->getDimension();
2010 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2011 core::dimension2d<u32> tile_size(size / tiles);
2012 s32 frame_count = strip_size.Height / strip_size.Width;
2013 if (frame_index >= frame_count)
2014 frame_index = frame_count - 1;
2015 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2016 video::IImage *result = nullptr;
2018 // extract crack frame
2019 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2022 if (tile_size == frame_size) {
2023 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2025 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2027 goto exit__has_tile;
2028 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2029 crack_frame->copyToScaling(crack_tile);
2030 crack_frame->drop();
2036 result = driver->createImage(video::ECF_A8R8G8B8, size);
2038 goto exit__has_tile;
2040 for (u8 i = 0; i < tiles; i++)
2041 for (u8 j = 0; j < tiles; j++)
2042 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2049 static void draw_crack(video::IImage *crack, video::IImage *dst,
2050 bool use_overlay, s32 frame_count, s32 progression,
2051 video::IVideoDriver *driver, u8 tiles)
2053 // Dimension of destination image
2054 core::dimension2d<u32> dim_dst = dst->getDimension();
2055 // Limit frame_count
2056 if (frame_count > (s32) dim_dst.Height)
2057 frame_count = dim_dst.Height;
2058 if (frame_count < 1)
2060 // Dimension of the scaled crack stage,
2061 // which is the same as the dimension of a single destination frame
2062 core::dimension2d<u32> frame_size(
2064 dim_dst.Height / frame_count
2066 video::IImage *crack_scaled = create_crack_image(crack, progression,
2067 frame_size, tiles, driver);
2071 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2072 for (s32 i = 0; i < frame_count; ++i) {
2073 v2s32 dst_pos(0, frame_size.Height * i);
2074 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2077 crack_scaled->drop();
2080 void brighten(video::IImage *image)
2085 core::dimension2d<u32> dim = image->getDimension();
2087 for (u32 y=0; y<dim.Height; y++)
2088 for (u32 x=0; x<dim.Width; x++)
2090 video::SColor c = image->getPixel(x,y);
2091 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2092 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2093 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2094 image->setPixel(x,y,c);
2098 u32 parseImageTransform(const std::string& s)
2100 int total_transform = 0;
2102 std::string transform_names[8];
2103 transform_names[0] = "i";
2104 transform_names[1] = "r90";
2105 transform_names[2] = "r180";
2106 transform_names[3] = "r270";
2107 transform_names[4] = "fx";
2108 transform_names[6] = "fy";
2110 std::size_t pos = 0;
2111 while(pos < s.size())
2114 for (int i = 0; i <= 7; ++i)
2116 const std::string &name_i = transform_names[i];
2118 if (s[pos] == ('0' + i))
2125 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2127 pos += name_i.size();
2134 // Multiply total_transform and transform in the group D4
2137 new_total = (transform + total_transform) % 4;
2139 new_total = (transform - total_transform + 8) % 4;
2140 if ((transform >= 4) ^ (total_transform >= 4))
2143 total_transform = new_total;
2145 return total_transform;
2148 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2150 if (transform % 2 == 0)
2153 return core::dimension2d<u32>(dim.Height, dim.Width);
2156 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2158 if (src == NULL || dst == NULL)
2161 core::dimension2d<u32> dstdim = dst->getDimension();
2164 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2165 assert(transform <= 7);
2168 Compute the transformation from source coordinates (sx,sy)
2169 to destination coordinates (dx,dy).
2173 if (transform == 0) // identity
2174 sxn = 0, syn = 2; // sx = dx, sy = dy
2175 else if (transform == 1) // rotate by 90 degrees ccw
2176 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2177 else if (transform == 2) // rotate by 180 degrees
2178 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2179 else if (transform == 3) // rotate by 270 degrees ccw
2180 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2181 else if (transform == 4) // flip x
2182 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2183 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2184 sxn = 2, syn = 0; // sx = dy, sy = dx
2185 else if (transform == 6) // flip y
2186 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2187 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2188 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2190 for (u32 dy=0; dy<dstdim.Height; dy++)
2191 for (u32 dx=0; dx<dstdim.Width; dx++)
2193 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2194 u32 sx = entries[sxn];
2195 u32 sy = entries[syn];
2196 video::SColor c = src->getPixel(sx,sy);
2197 dst->setPixel(dx,dy,c);
2201 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2203 if (isKnownSourceImage("override_normal.png"))
2204 return getTexture("override_normal.png");
2205 std::string fname_base = name;
2206 static const char *normal_ext = "_normal.png";
2207 static const u32 normal_ext_size = strlen(normal_ext);
2208 size_t pos = fname_base.find('.');
2209 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2210 if (isKnownSourceImage(fname_normal)) {
2211 // look for image extension and replace it
2213 while ((i = fname_base.find('.', i)) != std::string::npos) {
2214 fname_base.replace(i, 4, normal_ext);
2215 i += normal_ext_size;
2217 return getTexture(fname_base);
2222 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2224 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2225 video::SColor c(0, 0, 0, 0);
2226 video::ITexture *texture = getTexture(name);
2229 video::IImage *image = driver->createImage(texture,
2230 core::position2d<s32>(0, 0),
2231 texture->getOriginalSize());
2239 core::dimension2d<u32> dim = image->getDimension();
2242 step = dim.Width / 16;
2243 for (u16 x = 0; x < dim.Width; x += step) {
2244 for (u16 y = 0; y < dim.Width; y += step) {
2245 c = image->getPixel(x,y);
2246 if (c.getAlpha() > 0) {
2256 c.setRed(tR / total);
2257 c.setGreen(tG / total);
2258 c.setBlue(tB / total);
2265 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2267 std::string tname = "__shaderFlagsTexture";
2268 tname += normalmap_present ? "1" : "0";
2270 if (isKnownSourceImage(tname)) {
2271 return getTexture(tname);
2274 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2275 video::IImage *flags_image = driver->createImage(
2276 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2277 sanity_check(flags_image != NULL);
2278 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2279 flags_image->setPixel(0, 0, c);
2280 insertSourceImage(tname, flags_image);
2281 flags_image->drop();
2282 return getTexture(tname);
2286 std::vector<std::string> getTextureDirs()
2288 return fs::GetRecursiveDirs(g_settings->get("texture_path"));