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.
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
30 #include "util/strfnd.h"
31 #include "imagefilters.h"
32 #include "guiscalingfilter.h"
33 #include "renderingengine.h"
41 A cache from texture name to texture path
43 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
46 Replaces the filename extension.
48 std::string image = "a/image.png"
49 replace_ext(image, "jpg")
50 -> image = "a/image.jpg"
51 Returns true on success.
53 static bool replace_ext(std::string &path, const char *ext)
57 // Find place of last dot, fail if \ or / found.
59 for (s32 i=path.size()-1; i>=0; i--)
67 if (path[i] == '\\' || path[i] == '/')
70 // If not found, return an empty string
73 // Else make the new path
74 path = path.substr(0, last_dot_i+1) + ext;
79 Find out the full path of an image by trying different filename
84 std::string getImagePath(std::string path)
86 // A NULL-ended list of possible image extensions
87 const char *extensions[] = {
88 "png", "jpg", "bmp", "tga",
89 "pcx", "ppm", "psd", "wal", "rgb",
92 // If there is no extension, add one
93 if (removeStringEnd(path, extensions).empty())
95 // Check paths until something is found to exist
96 const char **ext = extensions;
98 bool r = replace_ext(path, *ext);
101 if (fs::PathExists(path))
104 while((++ext) != NULL);
110 Gets the path to a texture by first checking if the texture exists
111 in texture_path and if not, using the data path.
113 Checks all supported extensions by replacing the original extension.
115 If not found, returns "".
117 Utilizes a thread-safe cache.
119 std::string getTexturePath(const std::string &filename)
121 std::string fullpath;
125 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
130 Check from texture_path
132 for (const auto &path : getTextureDirs()) {
133 std::string testpath = path + DIR_DELIM + filename;
134 // Check all filename extensions. Returns "" if not found.
135 fullpath = getImagePath(testpath);
136 if (!fullpath.empty())
141 Check from default data directory
143 if (fullpath.empty())
145 std::string base_path = porting::path_share + DIR_DELIM + "textures"
146 + DIR_DELIM + "base" + DIR_DELIM + "pack";
147 std::string testpath = base_path + DIR_DELIM + filename;
148 // Check all filename extensions. Returns "" if not found.
149 fullpath = getImagePath(testpath);
152 // Add to cache (also an empty result is cached)
153 g_texturename_to_path_cache.set(filename, fullpath);
159 void clearTextureNameCache()
161 g_texturename_to_path_cache.clear();
165 Stores internal information about a texture.
171 video::ITexture *texture;
174 const std::string &name_,
175 video::ITexture *texture_=NULL
184 SourceImageCache: A cache used for storing source images.
187 class SourceImageCache
190 ~SourceImageCache() {
191 for (auto &m_image : m_images) {
192 m_image.second->drop();
196 void insert(const std::string &name, video::IImage *img, bool prefer_local)
198 assert(img); // Pre-condition
200 std::map<std::string, video::IImage*>::iterator n;
201 n = m_images.find(name);
202 if (n != m_images.end()){
207 video::IImage* toadd = img;
208 bool need_to_grab = true;
210 // Try to use local texture instead if asked to
212 std::string path = getTexturePath(name);
214 video::IImage *img2 = RenderingEngine::get_video_driver()->
215 createImageFromFile(path.c_str());
218 need_to_grab = false;
225 m_images[name] = toadd;
227 video::IImage* get(const std::string &name)
229 std::map<std::string, video::IImage*>::iterator n;
230 n = m_images.find(name);
231 if (n != m_images.end())
235 // Primarily fetches from cache, secondarily tries to read from filesystem
236 video::IImage *getOrLoad(const std::string &name)
238 std::map<std::string, video::IImage*>::iterator n;
239 n = m_images.find(name);
240 if (n != m_images.end()){
241 n->second->grab(); // Grab for caller
244 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
245 std::string path = getTexturePath(name);
247 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
248 <<name<<"\""<<std::endl;
251 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
253 video::IImage *img = driver->createImageFromFile(path.c_str());
256 m_images[name] = img;
257 img->grab(); // Grab for caller
262 std::map<std::string, video::IImage*> m_images;
269 class TextureSource : public IWritableTextureSource
273 virtual ~TextureSource();
277 Now, assume a texture with the id 1 exists, and has the name
278 "stone.png^mineral1".
279 Then a random thread calls getTextureId for a texture called
280 "stone.png^mineral1^crack0".
281 ...Now, WTF should happen? Well:
282 - getTextureId strips off stuff recursively from the end until
283 the remaining part is found, or nothing is left when
284 something is stripped out
286 But it is slow to search for textures by names and modify them
288 - ContentFeatures is made to contain ids for the basic plain
290 - Crack textures can be slow by themselves, but the framework
294 - Assume a texture with the id 1 exists, and has the name
295 "stone.png^mineral_coal.png".
296 - Now getNodeTile() stumbles upon a node which uses
297 texture id 1, and determines that MATERIAL_FLAG_CRACK
298 must be applied to the tile
299 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
300 has received the current crack level 0 from the client. It
301 finds out the name of the texture with getTextureName(1),
302 appends "^crack0" to it and gets a new texture id with
303 getTextureId("stone.png^mineral_coal.png^crack0").
308 Gets a texture id from cache or
309 - if main thread, generates the texture, adds to cache and returns id.
310 - if other thread, adds to request queue and waits for main thread.
312 The id 0 points to a NULL texture. It is returned in case of error.
314 u32 getTextureId(const std::string &name);
316 // Finds out the name of a cached texture.
317 std::string getTextureName(u32 id);
320 If texture specified by the name pointed by the id doesn't
321 exist, create it, then return the cached texture.
323 Can be called from any thread. If called from some other thread
324 and not found in cache, the call is queued to the main thread
327 video::ITexture* getTexture(u32 id);
329 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
332 Get a texture specifically intended for mesh
333 application, i.e. not HUD, compositing, or other 2D
334 use. This texture may be a different size and may
335 have had additional filters applied.
337 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
339 virtual Palette* getPalette(const std::string &name);
341 bool isKnownSourceImage(const std::string &name)
343 bool is_known = false;
344 bool cache_found = m_source_image_existence.get(name, &is_known);
347 // Not found in cache; find out if a local file exists
348 is_known = (!getTexturePath(name).empty());
349 m_source_image_existence.set(name, is_known);
353 // Processes queued texture requests from other threads.
354 // Shall be called from the main thread.
357 // Insert an image into the cache without touching the filesystem.
358 // Shall be called from the main thread.
359 void insertSourceImage(const std::string &name, video::IImage *img);
361 // Rebuild images and textures from the current set of source images
362 // Shall be called from the main thread.
363 void rebuildImagesAndTextures();
365 // Render a mesh to a texture.
366 // Returns NULL if render-to-texture failed.
367 // Shall be called from the main thread.
368 video::ITexture* generateTextureFromMesh(
369 const TextureFromMeshParams ¶ms);
371 video::ITexture* getNormalTexture(const std::string &name);
372 video::SColor getTextureAverageColor(const std::string &name);
373 video::ITexture *getShaderFlagsTexture(bool normamap_present);
377 // The id of the thread that is allowed to use irrlicht directly
378 std::thread::id m_main_thread;
380 // Cache of source images
381 // This should be only accessed from the main thread
382 SourceImageCache m_sourcecache;
384 // Generate a texture
385 u32 generateTexture(const std::string &name);
387 // Generate image based on a string like "stone.png" or "[crack:1:0".
388 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
389 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
391 /*! Generates an image from a full string like
392 * "stone.png^mineral_coal.png^[crack:1:0".
393 * Shall be called from the main thread.
394 * The returned Image should be dropped.
396 video::IImage* generateImage(const std::string &name);
398 // Thread-safe cache of what source images are known (true = known)
399 MutexedMap<std::string, bool> m_source_image_existence;
401 // A texture id is index in this array.
402 // The first position contains a NULL texture.
403 std::vector<TextureInfo> m_textureinfo_cache;
404 // Maps a texture name to an index in the former.
405 std::map<std::string, u32> m_name_to_id;
406 // The two former containers are behind this mutex
407 std::mutex m_textureinfo_cache_mutex;
409 // Queued texture fetches (to be processed by the main thread)
410 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
412 // Textures that have been overwritten with other ones
413 // but can't be deleted because the ITexture* might still be used
414 std::vector<video::ITexture*> m_texture_trash;
416 // Maps image file names to loaded palettes.
417 std::unordered_map<std::string, Palette> m_palettes;
419 // Cached settings needed for making textures from meshes
420 bool m_setting_trilinear_filter;
421 bool m_setting_bilinear_filter;
422 bool m_setting_anisotropic_filter;
425 IWritableTextureSource *createTextureSource()
427 return new TextureSource();
430 TextureSource::TextureSource()
432 m_main_thread = std::this_thread::get_id();
434 // Add a NULL TextureInfo as the first index, named ""
435 m_textureinfo_cache.emplace_back("");
436 m_name_to_id[""] = 0;
438 // Cache some settings
439 // Note: Since this is only done once, the game must be restarted
440 // for these settings to take effect
441 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
442 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
443 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
446 TextureSource::~TextureSource()
448 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
450 unsigned int textures_before = driver->getTextureCount();
452 for (const auto &iter : m_textureinfo_cache) {
455 driver->removeTexture(iter.texture);
457 m_textureinfo_cache.clear();
459 for (auto t : m_texture_trash) {
460 //cleanup trashed texture
461 driver->removeTexture(t);
464 infostream << "~TextureSource() "<< textures_before << "/"
465 << driver->getTextureCount() << std::endl;
468 u32 TextureSource::getTextureId(const std::string &name)
470 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
474 See if texture already exists
476 MutexAutoLock lock(m_textureinfo_cache_mutex);
477 std::map<std::string, u32>::iterator n;
478 n = m_name_to_id.find(name);
479 if (n != m_name_to_id.end())
488 if (std::this_thread::get_id() == m_main_thread) {
489 return generateTexture(name);
493 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
495 // We're gonna ask the result to be put into here
496 static ResultQueue<std::string, u32, u8, u8> result_queue;
498 // Throw a request in
499 m_get_texture_queue.add(name, 0, 0, &result_queue);
503 // Wait result for a second
504 GetResult<std::string, u32, u8, u8>
505 result = result_queue.pop_front(1000);
507 if (result.key == name) {
511 } catch(ItemNotFoundException &e) {
512 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
516 infostream << "getTextureId(): Failed" << std::endl;
521 // Draw an image on top of an another one, using the alpha channel of the
523 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
524 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
526 // Like blit_with_alpha, but only modifies destination pixels that
528 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
529 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
531 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
532 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
533 // color alpha with the destination alpha.
534 // Otherwise, any pixels that are not fully transparent get the color alpha.
535 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
536 const video::SColor &color, int ratio, bool keep_alpha);
538 // paint a texture using the given color
539 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
540 const video::SColor &color);
542 // Apply a mask to an image
543 static void apply_mask(video::IImage *mask, video::IImage *dst,
544 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
546 // Draw or overlay a crack
547 static void draw_crack(video::IImage *crack, video::IImage *dst,
548 bool use_overlay, s32 frame_count, s32 progression,
549 video::IVideoDriver *driver, u8 tiles = 1);
552 void brighten(video::IImage *image);
553 // Parse a transform name
554 u32 parseImageTransform(const std::string& s);
555 // Apply transform to image dimension
556 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
557 // Apply transform to image data
558 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
561 This method generates all the textures
563 u32 TextureSource::generateTexture(const std::string &name)
565 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
567 // Empty name means texture 0
569 infostream<<"generateTexture(): name is empty"<<std::endl;
575 See if texture already exists
577 MutexAutoLock lock(m_textureinfo_cache_mutex);
578 std::map<std::string, u32>::iterator n;
579 n = m_name_to_id.find(name);
580 if (n != m_name_to_id.end()) {
586 Calling only allowed from main thread
588 if (std::this_thread::get_id() != m_main_thread) {
589 errorstream<<"TextureSource::generateTexture() "
590 "called not from main thread"<<std::endl;
594 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
595 sanity_check(driver);
597 video::IImage *img = generateImage(name);
599 video::ITexture *tex = NULL;
603 img = Align2Npot2(img, driver);
605 // Create texture from resulting image
606 tex = driver->addTexture(name.c_str(), img);
607 guiScalingCache(io::path(name.c_str()), driver, img);
612 Add texture to caches (add NULL textures too)
615 MutexAutoLock lock(m_textureinfo_cache_mutex);
617 u32 id = m_textureinfo_cache.size();
618 TextureInfo ti(name, tex);
619 m_textureinfo_cache.push_back(ti);
620 m_name_to_id[name] = id;
625 std::string TextureSource::getTextureName(u32 id)
627 MutexAutoLock lock(m_textureinfo_cache_mutex);
629 if (id >= m_textureinfo_cache.size())
631 errorstream<<"TextureSource::getTextureName(): id="<<id
632 <<" >= m_textureinfo_cache.size()="
633 <<m_textureinfo_cache.size()<<std::endl;
637 return m_textureinfo_cache[id].name;
640 video::ITexture* TextureSource::getTexture(u32 id)
642 MutexAutoLock lock(m_textureinfo_cache_mutex);
644 if (id >= m_textureinfo_cache.size())
647 return m_textureinfo_cache[id].texture;
650 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
652 u32 actual_id = getTextureId(name);
656 return getTexture(actual_id);
659 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
661 return getTexture(name + "^[applyfiltersformesh", id);
664 Palette* TextureSource::getPalette(const std::string &name)
666 // Only the main thread may load images
667 sanity_check(std::this_thread::get_id() == m_main_thread);
672 auto it = m_palettes.find(name);
673 if (it == m_palettes.end()) {
675 video::IImage *img = generateImage(name);
677 warningstream << "TextureSource::getPalette(): palette \"" << name
678 << "\" could not be loaded." << std::endl;
682 u32 w = img->getDimension().Width;
683 u32 h = img->getDimension().Height;
684 // Real area of the image
689 warningstream << "TextureSource::getPalette(): the specified"
690 << " palette image \"" << name << "\" is larger than 256"
691 << " pixels, using the first 256." << std::endl;
693 } else if (256 % area != 0)
694 warningstream << "TextureSource::getPalette(): the "
695 << "specified palette image \"" << name << "\" does not "
696 << "contain power of two pixels." << std::endl;
697 // We stretch the palette so it will fit 256 values
698 // This many param2 values will have the same color
699 u32 step = 256 / area;
700 // For each pixel in the image
701 for (u32 i = 0; i < area; i++) {
702 video::SColor c = img->getPixel(i % w, i / w);
703 // Fill in palette with 'step' colors
704 for (u32 j = 0; j < step; j++)
705 new_palette.push_back(c);
708 // Fill in remaining elements
709 while (new_palette.size() < 256)
710 new_palette.emplace_back(0xFFFFFFFF);
711 m_palettes[name] = new_palette;
712 it = m_palettes.find(name);
714 if (it != m_palettes.end())
715 return &((*it).second);
719 void TextureSource::processQueue()
724 //NOTE this is only thread safe for ONE consumer thread!
725 if (!m_get_texture_queue.empty())
727 GetRequest<std::string, u32, u8, u8>
728 request = m_get_texture_queue.pop();
730 /*infostream<<"TextureSource::processQueue(): "
731 <<"got texture request with "
732 <<"name=\""<<request.key<<"\""
735 m_get_texture_queue.pushResult(request, generateTexture(request.key));
739 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
741 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
743 sanity_check(std::this_thread::get_id() == m_main_thread);
745 m_sourcecache.insert(name, img, true);
746 m_source_image_existence.set(name, true);
749 void TextureSource::rebuildImagesAndTextures()
751 MutexAutoLock lock(m_textureinfo_cache_mutex);
753 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
754 sanity_check(driver);
757 for (TextureInfo &ti : m_textureinfo_cache) {
758 video::IImage *img = generateImage(ti.name);
760 img = Align2Npot2(img, driver);
762 // Create texture from resulting image
763 video::ITexture *t = NULL;
765 t = driver->addTexture(ti.name.c_str(), img);
766 guiScalingCache(io::path(ti.name.c_str()), driver, img);
769 video::ITexture *t_old = ti.texture;
774 m_texture_trash.push_back(t_old);
778 video::ITexture* TextureSource::generateTextureFromMesh(
779 const TextureFromMeshParams ¶ms)
781 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
782 sanity_check(driver);
785 const GLubyte* renderstr = glGetString(GL_RENDERER);
786 std::string renderer((char*) renderstr);
788 // use no render to texture hack
790 (renderer.find("Adreno") != std::string::npos) ||
791 (renderer.find("Mali") != std::string::npos) ||
792 (renderer.find("Immersion") != std::string::npos) ||
793 (renderer.find("Tegra") != std::string::npos) ||
794 g_settings->getBool("inventory_image_hack")
796 // Get a scene manager
797 scene::ISceneManager *smgr_main = m_device->getSceneManager();
798 sanity_check(smgr_main);
799 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
802 const float scaling = 0.2;
804 scene::IMeshSceneNode* meshnode =
805 smgr->addMeshSceneNode(params.mesh, NULL,
806 -1, v3f(0,0,0), v3f(0,0,0),
807 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
808 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
809 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
810 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
811 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
812 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
814 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
815 params.camera_position, params.camera_lookat);
816 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
817 camera->setProjectionMatrix(params.camera_projection_matrix, false);
819 smgr->setAmbientLight(params.ambient_light);
820 smgr->addLightSceneNode(0,
821 params.light_position,
823 params.light_radius*scaling);
825 core::dimension2d<u32> screen = driver->getScreenSize();
828 driver->beginScene(true, true, video::SColor(0,0,0,0));
829 driver->clearZBuffer();
832 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
834 irr::video::IImage* rawImage =
835 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
837 u8* pixels = static_cast<u8*>(rawImage->lock());
844 core::rect<s32> source(
845 screen.Width /2 - (screen.Width * (scaling / 2)),
846 screen.Height/2 - (screen.Height * (scaling / 2)),
847 screen.Width /2 + (screen.Width * (scaling / 2)),
848 screen.Height/2 + (screen.Height * (scaling / 2))
851 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
852 partsize.Width, partsize.Height, GL_RGBA,
853 GL_UNSIGNED_BYTE, pixels);
857 // Drop scene manager
860 unsigned int pixelcount = partsize.Width*partsize.Height;
863 for (unsigned int i=0; i < pixelcount; i++) {
881 video::IImage* inventory_image =
882 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
884 rawImage->copyToScaling(inventory_image);
887 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
889 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
890 inventory_image->drop();
893 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
897 driver->makeColorKeyTexture(rtt, v2s32(0,0));
899 if (params.delete_texture_on_shutdown)
900 m_texture_trash.push_back(rtt);
906 if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) {
907 static bool warned = false;
910 errorstream<<"TextureSource::generateTextureFromMesh(): "
911 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
917 // Create render target texture
918 video::ITexture *rtt = driver->addRenderTargetTexture(
919 params.dim, params.rtt_texture_name.c_str(),
920 video::ECF_A8R8G8B8);
923 errorstream<<"TextureSource::generateTextureFromMesh(): "
924 <<"addRenderTargetTexture returned NULL."<<std::endl;
929 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
930 driver->removeTexture(rtt);
931 errorstream<<"TextureSource::generateTextureFromMesh(): "
932 <<"failed to set render target"<<std::endl;
936 // Get a scene manager
937 scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager();
939 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
942 scene::IMeshSceneNode* meshnode =
943 smgr->addMeshSceneNode(params.mesh, NULL,
944 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
945 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
946 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
947 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
948 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
949 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
951 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
952 params.camera_position, params.camera_lookat);
953 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
954 camera->setProjectionMatrix(params.camera_projection_matrix, false);
956 smgr->setAmbientLight(params.ambient_light);
957 smgr->addLightSceneNode(0,
958 params.light_position,
960 params.light_radius);
963 driver->beginScene(true, true, video::SColor(0,0,0,0));
967 // Drop scene manager
970 // Unset render target
971 driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
973 if (params.delete_texture_on_shutdown)
974 m_texture_trash.push_back(rtt);
979 video::IImage* TextureSource::generateImage(const std::string &name)
981 // Get the base image
983 const char separator = '^';
984 const char escape = '\\';
985 const char paren_open = '(';
986 const char paren_close = ')';
988 // Find last separator in the name
989 s32 last_separator_pos = -1;
991 for (s32 i = name.size() - 1; i >= 0; i--) {
992 if (i > 0 && name[i-1] == escape)
996 if (paren_bal == 0) {
997 last_separator_pos = i;
998 i = -1; // break out of loop
1002 if (paren_bal == 0) {
1003 errorstream << "generateImage(): unbalanced parentheses"
1004 << "(extranous '(') while generating texture \""
1005 << name << "\"" << std::endl;
1017 if (paren_bal > 0) {
1018 errorstream << "generateImage(): unbalanced parentheses"
1019 << "(missing matching '(') while generating texture \""
1020 << name << "\"" << std::endl;
1025 video::IImage *baseimg = NULL;
1028 If separator was found, make the base image
1029 using a recursive call.
1031 if (last_separator_pos != -1) {
1032 baseimg = generateImage(name.substr(0, last_separator_pos));
1036 Parse out the last part of the name of the image and act
1040 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1043 If this name is enclosed in parentheses, generate it
1044 and blit it onto the base image
1046 if (last_part_of_name[0] == paren_open
1047 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1048 std::string name2 = last_part_of_name.substr(1,
1049 last_part_of_name.size() - 2);
1050 video::IImage *tmp = generateImage(name2);
1052 errorstream << "generateImage(): "
1053 "Failed to generate \"" << name2 << "\""
1057 core::dimension2d<u32> dim = tmp->getDimension();
1059 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1064 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1065 // Generate image according to part of name
1066 errorstream << "generateImage(): "
1067 "Failed to generate \"" << last_part_of_name << "\""
1071 // If no resulting image, print a warning
1072 if (baseimg == NULL) {
1073 errorstream << "generateImage(): baseimg is NULL (attempted to"
1074 " create texture \"" << name << "\")" << std::endl;
1081 #include <GLES/gl.h>
1083 * Check and align image to npot2 if required by hardware
1084 * @param image image to check for npot2 alignment
1085 * @param driver driver to use for image operations
1086 * @return image or copy of image aligned to npot2
1089 inline u16 get_GL_major_version()
1091 const GLubyte *gl_version = glGetString(GL_VERSION);
1092 return (u16) (gl_version[0] - '0');
1095 video::IImage * Align2Npot2(video::IImage * image,
1096 video::IVideoDriver* driver)
1098 if (image == NULL) {
1102 core::dimension2d<u32> dim = image->getDimension();
1104 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1106 // Only GLES2 is trusted to correctly report npot support
1107 if (get_GL_major_version() > 1 &&
1108 extensions.find("GL_OES_texture_npot") != std::string::npos) {
1112 unsigned int height = npot2(dim.Height);
1113 unsigned int width = npot2(dim.Width);
1115 if ((dim.Height == height) &&
1116 (dim.Width == width)) {
1120 if (dim.Height > height) {
1124 if (dim.Width > width) {
1128 video::IImage *targetimage =
1129 driver->createImage(video::ECF_A8R8G8B8,
1130 core::dimension2d<u32>(width, height));
1132 if (targetimage != NULL) {
1133 image->copyToScaling(targetimage);
1141 static std::string unescape_string(const std::string &str, const char esc = '\\')
1144 size_t pos = 0, cpos;
1145 out.reserve(str.size());
1147 cpos = str.find_first_of(esc, pos);
1148 if (cpos == std::string::npos) {
1149 out += str.substr(pos);
1152 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1158 bool TextureSource::generateImagePart(std::string part_of_name,
1159 video::IImage *& baseimg)
1161 const char escape = '\\'; // same as in generateImage()
1162 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1163 sanity_check(driver);
1165 // Stuff starting with [ are special commands
1166 if (part_of_name.empty() || part_of_name[0] != '[') {
1167 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1169 image = Align2Npot2(image, driver);
1171 if (image == NULL) {
1172 if (!part_of_name.empty()) {
1174 // Do not create normalmap dummies
1175 if (part_of_name.find("_normal.png") != std::string::npos) {
1176 warningstream << "generateImage(): Could not load normal map \""
1177 << part_of_name << "\"" << std::endl;
1181 errorstream << "generateImage(): Could not load image \""
1182 << part_of_name << "\" while building texture; "
1183 "Creating a dummy image" << std::endl;
1186 // Just create a dummy image
1187 //core::dimension2d<u32> dim(2,2);
1188 core::dimension2d<u32> dim(1,1);
1189 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1190 sanity_check(image != NULL);
1191 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1192 image->setPixel(1,0, video::SColor(255,0,255,0));
1193 image->setPixel(0,1, video::SColor(255,0,0,255));
1194 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1195 image->setPixel(0,0, video::SColor(255,myrand()%256,
1196 myrand()%256,myrand()%256));
1197 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1198 myrand()%256,myrand()%256));
1199 image->setPixel(0,1, video::SColor(255,myrand()%256,
1200 myrand()%256,myrand()%256));
1201 image->setPixel(1,1, video::SColor(255,myrand()%256,
1202 myrand()%256,myrand()%256));*/
1205 // If base image is NULL, load as base.
1206 if (baseimg == NULL)
1208 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1210 Copy it this way to get an alpha channel.
1211 Otherwise images with alpha cannot be blitted on
1212 images that don't have alpha in the original file.
1214 core::dimension2d<u32> dim = image->getDimension();
1215 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1216 image->copyTo(baseimg);
1218 // Else blit on base.
1221 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1222 // Size of the copied area
1223 core::dimension2d<u32> dim = image->getDimension();
1224 //core::dimension2d<u32> dim(16,16);
1225 // Position to copy the blitted to in the base image
1226 core::position2d<s32> pos_to(0,0);
1227 // Position to copy the blitted from in the blitted image
1228 core::position2d<s32> pos_from(0,0);
1230 /*image->copyToWithAlpha(baseimg, pos_to,
1231 core::rect<s32>(pos_from, dim),
1232 video::SColor(255,255,255,255),
1235 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1236 if (dim == dim_dst) {
1237 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1238 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1239 // Upscale overlying image
1240 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1241 createImage(video::ECF_A8R8G8B8, dim_dst);
1242 image->copyToScaling(scaled_image);
1244 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1245 scaled_image->drop();
1247 // Upscale base image
1248 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1249 createImage(video::ECF_A8R8G8B8, dim);
1250 baseimg->copyToScaling(scaled_base);
1252 baseimg = scaled_base;
1254 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1262 // A special texture modification
1264 /*infostream<<"generateImage(): generating special "
1265 <<"modification \""<<part_of_name<<"\""
1271 Adds a cracking texture
1272 N = animation frame count, P = crack progression
1274 if (str_starts_with(part_of_name, "[crack"))
1276 if (baseimg == NULL) {
1277 errorstream<<"generateImagePart(): baseimg == NULL "
1278 <<"for part_of_name=\""<<part_of_name
1279 <<"\", cancelling."<<std::endl;
1283 // Crack image number and overlay option
1284 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1285 bool use_overlay = (part_of_name[6] == 'o');
1286 Strfnd sf(part_of_name);
1288 s32 frame_count = stoi(sf.next(":"));
1289 s32 progression = stoi(sf.next(":"));
1291 // Check whether there is the <tiles> argument, that is,
1292 // whether there are 3 arguments. If so, shift values
1293 // as the first and not the last argument is optional.
1294 auto s = sf.next(":");
1296 tiles = frame_count;
1297 frame_count = progression;
1298 progression = stoi(s);
1301 if (progression >= 0) {
1305 It is an image with a number of cracking stages
1308 video::IImage *img_crack = m_sourcecache.getOrLoad(
1309 "crack_anylength.png");
1312 draw_crack(img_crack, baseimg,
1313 use_overlay, frame_count,
1314 progression, driver, tiles);
1320 [combine:WxH:X,Y=filename:X,Y=filename2
1321 Creates a bigger texture from any amount of smaller ones
1323 else if (str_starts_with(part_of_name, "[combine"))
1325 Strfnd sf(part_of_name);
1327 u32 w0 = stoi(sf.next("x"));
1328 u32 h0 = stoi(sf.next(":"));
1329 core::dimension2d<u32> dim(w0,h0);
1330 if (baseimg == NULL) {
1331 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1332 baseimg->fill(video::SColor(0,0,0,0));
1334 while (!sf.at_end()) {
1335 u32 x = stoi(sf.next(","));
1336 u32 y = stoi(sf.next("="));
1337 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1338 infostream<<"Adding \""<<filename
1339 <<"\" to combined ("<<x<<","<<y<<")"
1341 video::IImage *img = generateImage(filename);
1343 core::dimension2d<u32> dim = img->getDimension();
1344 infostream<<"Size "<<dim.Width
1345 <<"x"<<dim.Height<<std::endl;
1346 core::position2d<s32> pos_base(x, y);
1347 video::IImage *img2 =
1348 driver->createImage(video::ECF_A8R8G8B8, dim);
1351 /*img2->copyToWithAlpha(baseimg, pos_base,
1352 core::rect<s32>(v2s32(0,0), dim),
1353 video::SColor(255,255,255,255),
1355 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1358 errorstream << "generateImagePart(): Failed to load image \""
1359 << filename << "\" for [combine" << std::endl;
1366 else if (str_starts_with(part_of_name, "[brighten"))
1368 if (baseimg == NULL) {
1369 errorstream<<"generateImagePart(): baseimg==NULL "
1370 <<"for part_of_name=\""<<part_of_name
1371 <<"\", cancelling."<<std::endl;
1379 Make image completely opaque.
1380 Used for the leaves texture when in old leaves mode, so
1381 that the transparent parts don't look completely black
1382 when simple alpha channel is used for rendering.
1384 else if (str_starts_with(part_of_name, "[noalpha"))
1386 if (baseimg == NULL){
1387 errorstream<<"generateImagePart(): baseimg==NULL "
1388 <<"for part_of_name=\""<<part_of_name
1389 <<"\", cancelling."<<std::endl;
1393 core::dimension2d<u32> dim = baseimg->getDimension();
1395 // Set alpha to full
1396 for (u32 y=0; y<dim.Height; y++)
1397 for (u32 x=0; x<dim.Width; x++)
1399 video::SColor c = baseimg->getPixel(x,y);
1401 baseimg->setPixel(x,y,c);
1406 Convert one color to transparent.
1408 else if (str_starts_with(part_of_name, "[makealpha:"))
1410 if (baseimg == NULL) {
1411 errorstream<<"generateImagePart(): baseimg == NULL "
1412 <<"for part_of_name=\""<<part_of_name
1413 <<"\", cancelling."<<std::endl;
1417 Strfnd sf(part_of_name.substr(11));
1418 u32 r1 = stoi(sf.next(","));
1419 u32 g1 = stoi(sf.next(","));
1420 u32 b1 = stoi(sf.next(""));
1422 core::dimension2d<u32> dim = baseimg->getDimension();
1424 /*video::IImage *oldbaseimg = baseimg;
1425 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1426 oldbaseimg->copyTo(baseimg);
1427 oldbaseimg->drop();*/
1429 // Set alpha to full
1430 for (u32 y=0; y<dim.Height; y++)
1431 for (u32 x=0; x<dim.Width; x++)
1433 video::SColor c = baseimg->getPixel(x,y);
1435 u32 g = c.getGreen();
1436 u32 b = c.getBlue();
1437 if (!(r == r1 && g == g1 && b == b1))
1440 baseimg->setPixel(x,y,c);
1445 Rotates and/or flips the image.
1447 N can be a number (between 0 and 7) or a transform name.
1448 Rotations are counter-clockwise.
1450 1 R90 rotate by 90 degrees
1451 2 R180 rotate by 180 degrees
1452 3 R270 rotate by 270 degrees
1454 5 FXR90 flip X then rotate by 90 degrees
1456 7 FYR90 flip Y then rotate by 90 degrees
1458 Note: Transform names can be concatenated to produce
1459 their product (applies the first then the second).
1460 The resulting transform will be equivalent to one of the
1461 eight existing ones, though (see: dihedral group).
1463 else if (str_starts_with(part_of_name, "[transform"))
1465 if (baseimg == NULL) {
1466 errorstream<<"generateImagePart(): baseimg == NULL "
1467 <<"for part_of_name=\""<<part_of_name
1468 <<"\", cancelling."<<std::endl;
1472 u32 transform = parseImageTransform(part_of_name.substr(10));
1473 core::dimension2d<u32> dim = imageTransformDimension(
1474 transform, baseimg->getDimension());
1475 video::IImage *image = driver->createImage(
1476 baseimg->getColorFormat(), dim);
1477 sanity_check(image != NULL);
1478 imageTransform(transform, baseimg, image);
1483 [inventorycube{topimage{leftimage{rightimage
1484 In every subimage, replace ^ with &.
1485 Create an "inventory cube".
1486 NOTE: This should be used only on its own.
1487 Example (a grass block (not actually used in game):
1488 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1490 else if (str_starts_with(part_of_name, "[inventorycube"))
1492 if (baseimg != NULL){
1493 errorstream<<"generateImagePart(): baseimg != NULL "
1494 <<"for part_of_name=\""<<part_of_name
1495 <<"\", cancelling."<<std::endl;
1499 str_replace(part_of_name, '&', '^');
1500 Strfnd sf(part_of_name);
1502 std::string imagename_top = sf.next("{");
1503 std::string imagename_left = sf.next("{");
1504 std::string imagename_right = sf.next("{");
1506 // Generate images for the faces of the cube
1507 video::IImage *img_top = generateImage(imagename_top);
1508 video::IImage *img_left = generateImage(imagename_left);
1509 video::IImage *img_right = generateImage(imagename_right);
1511 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1512 errorstream << "generateImagePart(): Failed to create textures"
1513 << " for inventorycube \"" << part_of_name << "\""
1515 baseimg = generateImage(imagename_top);
1520 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1521 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1523 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1524 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1526 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1527 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1530 // Create textures from images
1531 video::ITexture *texture_top = driver->addTexture(
1532 (imagename_top + "__temp__").c_str(), img_top);
1533 video::ITexture *texture_left = driver->addTexture(
1534 (imagename_left + "__temp__").c_str(), img_left);
1535 video::ITexture *texture_right = driver->addTexture(
1536 (imagename_right + "__temp__").c_str(), img_right);
1537 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1545 Draw a cube mesh into a render target texture
1547 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1548 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1549 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1550 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1551 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1552 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1553 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1554 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1556 TextureFromMeshParams params;
1558 params.dim.set(64, 64);
1559 params.rtt_texture_name = part_of_name + "_RTT";
1560 // We will delete the rtt texture ourselves
1561 params.delete_texture_on_shutdown = false;
1562 params.camera_position.set(0, 1.0, -1.5);
1563 params.camera_position.rotateXZBy(45);
1564 params.camera_lookat.set(0, 0, 0);
1565 // Set orthogonal projection
1566 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1567 1.65, 1.65, 0, 100);
1569 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1570 params.light_position.set(10, 100, -50);
1571 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1572 params.light_radius = 1000;
1574 video::ITexture *rtt = generateTextureFromMesh(params);
1580 driver->removeTexture(texture_top);
1581 driver->removeTexture(texture_left);
1582 driver->removeTexture(texture_right);
1585 baseimg = generateImage(imagename_top);
1589 // Create image of render target
1590 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1591 FATAL_ERROR_IF(!image, "Could not create image of render target");
1594 driver->removeTexture(rtt);
1596 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1599 image->copyTo(baseimg);
1604 [lowpart:percent:filename
1605 Adds the lower part of a texture
1607 else if (str_starts_with(part_of_name, "[lowpart:"))
1609 Strfnd sf(part_of_name);
1611 u32 percent = stoi(sf.next(":"));
1612 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1614 if (baseimg == NULL)
1615 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1616 video::IImage *img = generateImage(filename);
1619 core::dimension2d<u32> dim = img->getDimension();
1620 core::position2d<s32> pos_base(0, 0);
1621 video::IImage *img2 =
1622 driver->createImage(video::ECF_A8R8G8B8, dim);
1625 core::position2d<s32> clippos(0, 0);
1626 clippos.Y = dim.Height * (100-percent) / 100;
1627 core::dimension2d<u32> clipdim = dim;
1628 clipdim.Height = clipdim.Height * percent / 100 + 1;
1629 core::rect<s32> cliprect(clippos, clipdim);
1630 img2->copyToWithAlpha(baseimg, pos_base,
1631 core::rect<s32>(v2s32(0,0), dim),
1632 video::SColor(255,255,255,255),
1639 Crops a frame of a vertical animation.
1640 N = frame count, I = frame index
1642 else if (str_starts_with(part_of_name, "[verticalframe:"))
1644 Strfnd sf(part_of_name);
1646 u32 frame_count = stoi(sf.next(":"));
1647 u32 frame_index = stoi(sf.next(":"));
1649 if (baseimg == NULL){
1650 errorstream<<"generateImagePart(): baseimg != NULL "
1651 <<"for part_of_name=\""<<part_of_name
1652 <<"\", cancelling."<<std::endl;
1656 v2u32 frame_size = baseimg->getDimension();
1657 frame_size.Y /= frame_count;
1659 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1662 errorstream<<"generateImagePart(): Could not create image "
1663 <<"for part_of_name=\""<<part_of_name
1664 <<"\", cancelling."<<std::endl;
1668 // Fill target image with transparency
1669 img->fill(video::SColor(0,0,0,0));
1671 core::dimension2d<u32> dim = frame_size;
1672 core::position2d<s32> pos_dst(0, 0);
1673 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1674 baseimg->copyToWithAlpha(img, pos_dst,
1675 core::rect<s32>(pos_src, dim),
1676 video::SColor(255,255,255,255),
1684 Applies a mask to an image
1686 else if (str_starts_with(part_of_name, "[mask:"))
1688 if (baseimg == NULL) {
1689 errorstream << "generateImage(): baseimg == NULL "
1690 << "for part_of_name=\"" << part_of_name
1691 << "\", cancelling." << std::endl;
1694 Strfnd sf(part_of_name);
1696 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1698 video::IImage *img = generateImage(filename);
1700 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1701 img->getDimension());
1704 errorstream << "generateImage(): Failed to load \""
1705 << filename << "\".";
1710 multiplys a given color to any pixel of an image
1711 color = color as ColorString
1713 else if (str_starts_with(part_of_name, "[multiply:")) {
1714 Strfnd sf(part_of_name);
1716 std::string color_str = sf.next(":");
1718 if (baseimg == NULL) {
1719 errorstream << "generateImagePart(): baseimg != NULL "
1720 << "for part_of_name=\"" << part_of_name
1721 << "\", cancelling." << std::endl;
1725 video::SColor color;
1727 if (!parseColorString(color_str, color, false))
1730 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1734 Overlays image with given color
1735 color = color as ColorString
1737 else if (str_starts_with(part_of_name, "[colorize:"))
1739 Strfnd sf(part_of_name);
1741 std::string color_str = sf.next(":");
1742 std::string ratio_str = sf.next(":");
1744 if (baseimg == NULL) {
1745 errorstream << "generateImagePart(): baseimg != NULL "
1746 << "for part_of_name=\"" << part_of_name
1747 << "\", cancelling." << std::endl;
1751 video::SColor color;
1753 bool keep_alpha = false;
1755 if (!parseColorString(color_str, color, false))
1758 if (is_number(ratio_str))
1759 ratio = mystoi(ratio_str, 0, 255);
1760 else if (ratio_str == "alpha")
1763 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1766 [applyfiltersformesh
1769 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1771 // Apply the "clean transparent" filter, if configured.
1772 if (g_settings->getBool("texture_clean_transparent"))
1773 imageCleanTransparent(baseimg, 127);
1775 /* Upscale textures to user's requested minimum size. This is a trick to make
1776 * filters look as good on low-res textures as on high-res ones, by making
1777 * low-res textures BECOME high-res ones. This is helpful for worlds that
1778 * mix high- and low-res textures, or for mods with least-common-denominator
1779 * textures that don't have the resources to offer high-res alternatives.
1781 const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
1782 const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
1784 const core::dimension2d<u32> dim = baseimg->getDimension();
1786 /* Calculate scaling needed to make the shortest texture dimension
1787 * equal to the target minimum. If e.g. this is a vertical frames
1788 * animation, the short dimension will be the real size.
1790 if ((dim.Width == 0) || (dim.Height == 0)) {
1791 errorstream << "generateImagePart(): Illegal 0 dimension "
1792 << "for part_of_name=\""<< part_of_name
1793 << "\", cancelling." << std::endl;
1796 u32 xscale = scaleto / dim.Width;
1797 u32 yscale = scaleto / dim.Height;
1798 u32 scale = (xscale > yscale) ? xscale : yscale;
1800 // Never downscale; only scale up by 2x or more.
1802 u32 w = scale * dim.Width;
1803 u32 h = scale * dim.Height;
1804 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1805 video::IImage *newimg = driver->createImage(
1806 baseimg->getColorFormat(), newdim);
1807 baseimg->copyToScaling(newimg);
1815 Resizes the base image to the given dimensions
1817 else if (str_starts_with(part_of_name, "[resize"))
1819 if (baseimg == NULL) {
1820 errorstream << "generateImagePart(): baseimg == NULL "
1821 << "for part_of_name=\""<< part_of_name
1822 << "\", cancelling." << std::endl;
1826 Strfnd sf(part_of_name);
1828 u32 width = stoi(sf.next("x"));
1829 u32 height = stoi(sf.next(""));
1830 core::dimension2d<u32> dim(width, height);
1832 video::IImage *image = RenderingEngine::get_video_driver()->
1833 createImage(video::ECF_A8R8G8B8, dim);
1834 baseimg->copyToScaling(image);
1840 Makes the base image transparent according to the given ratio.
1841 R must be between 0 and 255.
1842 0 means totally transparent.
1843 255 means totally opaque.
1845 else if (str_starts_with(part_of_name, "[opacity:")) {
1846 if (baseimg == NULL) {
1847 errorstream << "generateImagePart(): baseimg == NULL "
1848 << "for part_of_name=\"" << part_of_name
1849 << "\", cancelling." << std::endl;
1853 Strfnd sf(part_of_name);
1856 u32 ratio = mystoi(sf.next(""), 0, 255);
1858 core::dimension2d<u32> dim = baseimg->getDimension();
1860 for (u32 y = 0; y < dim.Height; y++)
1861 for (u32 x = 0; x < dim.Width; x++)
1863 video::SColor c = baseimg->getPixel(x, y);
1864 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1865 baseimg->setPixel(x, y, c);
1870 Inverts the given channels of the base image.
1871 Mode may contain the characters "r", "g", "b", "a".
1872 Only the channels that are mentioned in the mode string
1875 else if (str_starts_with(part_of_name, "[invert:")) {
1876 if (baseimg == NULL) {
1877 errorstream << "generateImagePart(): baseimg == NULL "
1878 << "for part_of_name=\"" << part_of_name
1879 << "\", cancelling." << std::endl;
1883 Strfnd sf(part_of_name);
1886 std::string mode = sf.next("");
1888 if (mode.find('a') != std::string::npos)
1889 mask |= 0xff000000UL;
1890 if (mode.find('r') != std::string::npos)
1891 mask |= 0x00ff0000UL;
1892 if (mode.find('g') != std::string::npos)
1893 mask |= 0x0000ff00UL;
1894 if (mode.find('b') != std::string::npos)
1895 mask |= 0x000000ffUL;
1897 core::dimension2d<u32> dim = baseimg->getDimension();
1899 for (u32 y = 0; y < dim.Height; y++)
1900 for (u32 x = 0; x < dim.Width; x++)
1902 video::SColor c = baseimg->getPixel(x, y);
1904 baseimg->setPixel(x, y, c);
1909 Retrieves a tile at position X,Y (in tiles)
1910 from the base image it assumes to be a
1911 tilesheet with dimensions W,H (in tiles).
1913 else if (part_of_name.substr(0,7) == "[sheet:") {
1914 if (baseimg == NULL) {
1915 errorstream << "generateImagePart(): baseimg != NULL "
1916 << "for part_of_name=\"" << part_of_name
1917 << "\", cancelling." << std::endl;
1921 Strfnd sf(part_of_name);
1923 u32 w0 = stoi(sf.next("x"));
1924 u32 h0 = stoi(sf.next(":"));
1925 u32 x0 = stoi(sf.next(","));
1926 u32 y0 = stoi(sf.next(":"));
1928 core::dimension2d<u32> img_dim = baseimg->getDimension();
1929 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1931 video::IImage *img = driver->createImage(
1932 video::ECF_A8R8G8B8, tile_dim);
1934 errorstream << "generateImagePart(): Could not create image "
1935 << "for part_of_name=\"" << part_of_name
1936 << "\", cancelling." << std::endl;
1940 img->fill(video::SColor(0,0,0,0));
1941 v2u32 vdim(tile_dim);
1942 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1943 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1944 video::SColor(255,255,255,255), NULL);
1952 errorstream << "generateImagePart(): Invalid "
1953 " modification: \"" << part_of_name << "\"" << std::endl;
1961 Draw an image on top of an another one, using the alpha channel of the
1964 This exists because IImage::copyToWithAlpha() doesn't seem to always
1967 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1968 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1970 for (u32 y0=0; y0<size.Y; y0++)
1971 for (u32 x0=0; x0<size.X; x0++)
1973 s32 src_x = src_pos.X + x0;
1974 s32 src_y = src_pos.Y + y0;
1975 s32 dst_x = dst_pos.X + x0;
1976 s32 dst_y = dst_pos.Y + y0;
1977 video::SColor src_c = src->getPixel(src_x, src_y);
1978 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1979 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1980 dst->setPixel(dst_x, dst_y, dst_c);
1985 Draw an image on top of an another one, using the alpha channel of the
1986 source image; only modify fully opaque pixels in destinaion
1988 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1989 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1991 for (u32 y0=0; y0<size.Y; y0++)
1992 for (u32 x0=0; x0<size.X; x0++)
1994 s32 src_x = src_pos.X + x0;
1995 s32 src_y = src_pos.Y + y0;
1996 s32 dst_x = dst_pos.X + x0;
1997 s32 dst_y = dst_pos.Y + y0;
1998 video::SColor src_c = src->getPixel(src_x, src_y);
1999 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2000 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
2002 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2003 dst->setPixel(dst_x, dst_y, dst_c);
2008 // This function has been disabled because it is currently unused.
2009 // Feel free to re-enable if you find it handy.
2012 Draw an image on top of an another one, using the specified ratio
2013 modify all partially-opaque pixels in the destination.
2015 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2016 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2018 for (u32 y0 = 0; y0 < size.Y; y0++)
2019 for (u32 x0 = 0; x0 < size.X; x0++)
2021 s32 src_x = src_pos.X + x0;
2022 s32 src_y = src_pos.Y + y0;
2023 s32 dst_x = dst_pos.X + x0;
2024 s32 dst_y = dst_pos.Y + y0;
2025 video::SColor src_c = src->getPixel(src_x, src_y);
2026 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2027 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2030 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2032 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2033 dst->setPixel(dst_x, dst_y, dst_c);
2040 Apply color to destination
2042 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2043 const video::SColor &color, int ratio, bool keep_alpha)
2045 u32 alpha = color.getAlpha();
2046 video::SColor dst_c;
2047 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2048 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2050 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2051 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2052 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2053 if (dst_alpha > 0) {
2054 dst_c.setAlpha(dst_alpha * alpha / 255);
2055 dst->setPixel(x, y, dst_c);
2058 } else { // replace the color including the alpha
2059 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2060 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2061 if (dst->getPixel(x, y).getAlpha() > 0)
2062 dst->setPixel(x, y, color);
2064 } else { // interpolate between the color and destination
2065 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2066 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2067 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2068 dst_c = dst->getPixel(x, y);
2069 if (dst_c.getAlpha() > 0) {
2070 dst_c = color.getInterpolated(dst_c, interp);
2071 dst->setPixel(x, y, dst_c);
2078 Apply color to destination
2080 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2081 const video::SColor &color)
2083 video::SColor dst_c;
2085 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2086 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2087 dst_c = dst->getPixel(x, y);
2090 (dst_c.getRed() * color.getRed()) / 255,
2091 (dst_c.getGreen() * color.getGreen()) / 255,
2092 (dst_c.getBlue() * color.getBlue()) / 255
2094 dst->setPixel(x, y, dst_c);
2099 Apply mask to destination
2101 static void apply_mask(video::IImage *mask, video::IImage *dst,
2102 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2104 for (u32 y0 = 0; y0 < size.Y; y0++) {
2105 for (u32 x0 = 0; x0 < size.X; x0++) {
2106 s32 mask_x = x0 + mask_pos.X;
2107 s32 mask_y = y0 + mask_pos.Y;
2108 s32 dst_x = x0 + dst_pos.X;
2109 s32 dst_y = y0 + dst_pos.Y;
2110 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2111 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2112 dst_c.color &= mask_c.color;
2113 dst->setPixel(dst_x, dst_y, dst_c);
2118 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2119 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2121 core::dimension2d<u32> strip_size = crack->getDimension();
2122 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2123 core::dimension2d<u32> tile_size(size / tiles);
2124 s32 frame_count = strip_size.Height / strip_size.Width;
2125 if (frame_index >= frame_count)
2126 frame_index = frame_count - 1;
2127 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2128 video::IImage *result = nullptr;
2130 // extract crack frame
2131 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2134 if (tile_size == frame_size) {
2135 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2137 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2139 goto exit__has_tile;
2140 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2141 crack_frame->copyToScaling(crack_tile);
2142 crack_frame->drop();
2148 result = driver->createImage(video::ECF_A8R8G8B8, size);
2150 goto exit__has_tile;
2152 for (u8 i = 0; i < tiles; i++)
2153 for (u8 j = 0; j < tiles; j++)
2154 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2161 static void draw_crack(video::IImage *crack, video::IImage *dst,
2162 bool use_overlay, s32 frame_count, s32 progression,
2163 video::IVideoDriver *driver, u8 tiles)
2165 // Dimension of destination image
2166 core::dimension2d<u32> dim_dst = dst->getDimension();
2167 // Limit frame_count
2168 if (frame_count > (s32) dim_dst.Height)
2169 frame_count = dim_dst.Height;
2170 if (frame_count < 1)
2172 // Dimension of the scaled crack stage,
2173 // which is the same as the dimension of a single destination frame
2174 core::dimension2d<u32> frame_size(
2176 dim_dst.Height / frame_count
2178 video::IImage *crack_scaled = create_crack_image(crack, progression,
2179 frame_size, tiles, driver);
2183 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2184 for (s32 i = 0; i < frame_count; ++i) {
2185 v2s32 dst_pos(0, frame_size.Height * i);
2186 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2189 crack_scaled->drop();
2192 void brighten(video::IImage *image)
2197 core::dimension2d<u32> dim = image->getDimension();
2199 for (u32 y=0; y<dim.Height; y++)
2200 for (u32 x=0; x<dim.Width; x++)
2202 video::SColor c = image->getPixel(x,y);
2203 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2204 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2205 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2206 image->setPixel(x,y,c);
2210 u32 parseImageTransform(const std::string& s)
2212 int total_transform = 0;
2214 std::string transform_names[8];
2215 transform_names[0] = "i";
2216 transform_names[1] = "r90";
2217 transform_names[2] = "r180";
2218 transform_names[3] = "r270";
2219 transform_names[4] = "fx";
2220 transform_names[6] = "fy";
2222 std::size_t pos = 0;
2223 while(pos < s.size())
2226 for (int i = 0; i <= 7; ++i)
2228 const std::string &name_i = transform_names[i];
2230 if (s[pos] == ('0' + i))
2237 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2239 pos += name_i.size();
2246 // Multiply total_transform and transform in the group D4
2249 new_total = (transform + total_transform) % 4;
2251 new_total = (transform - total_transform + 8) % 4;
2252 if ((transform >= 4) ^ (total_transform >= 4))
2255 total_transform = new_total;
2257 return total_transform;
2260 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2262 if (transform % 2 == 0)
2265 return core::dimension2d<u32>(dim.Height, dim.Width);
2268 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2270 if (src == NULL || dst == NULL)
2273 core::dimension2d<u32> dstdim = dst->getDimension();
2276 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2277 assert(transform <= 7);
2280 Compute the transformation from source coordinates (sx,sy)
2281 to destination coordinates (dx,dy).
2285 if (transform == 0) // identity
2286 sxn = 0, syn = 2; // sx = dx, sy = dy
2287 else if (transform == 1) // rotate by 90 degrees ccw
2288 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2289 else if (transform == 2) // rotate by 180 degrees
2290 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2291 else if (transform == 3) // rotate by 270 degrees ccw
2292 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2293 else if (transform == 4) // flip x
2294 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2295 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2296 sxn = 2, syn = 0; // sx = dy, sy = dx
2297 else if (transform == 6) // flip y
2298 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2299 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2300 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2302 for (u32 dy=0; dy<dstdim.Height; dy++)
2303 for (u32 dx=0; dx<dstdim.Width; dx++)
2305 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2306 u32 sx = entries[sxn];
2307 u32 sy = entries[syn];
2308 video::SColor c = src->getPixel(sx,sy);
2309 dst->setPixel(dx,dy,c);
2313 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2315 if (isKnownSourceImage("override_normal.png"))
2316 return getTexture("override_normal.png");
2317 std::string fname_base = name;
2318 static const char *normal_ext = "_normal.png";
2319 static const u32 normal_ext_size = strlen(normal_ext);
2320 size_t pos = fname_base.find('.');
2321 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2322 if (isKnownSourceImage(fname_normal)) {
2323 // look for image extension and replace it
2325 while ((i = fname_base.find('.', i)) != std::string::npos) {
2326 fname_base.replace(i, 4, normal_ext);
2327 i += normal_ext_size;
2329 return getTexture(fname_base);
2334 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2336 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2337 video::SColor c(0, 0, 0, 0);
2338 video::ITexture *texture = getTexture(name);
2339 video::IImage *image = driver->createImage(texture,
2340 core::position2d<s32>(0, 0),
2341 texture->getOriginalSize());
2346 core::dimension2d<u32> dim = image->getDimension();
2349 step = dim.Width / 16;
2350 for (u16 x = 0; x < dim.Width; x += step) {
2351 for (u16 y = 0; y < dim.Width; y += step) {
2352 c = image->getPixel(x,y);
2353 if (c.getAlpha() > 0) {
2363 c.setRed(tR / total);
2364 c.setGreen(tG / total);
2365 c.setBlue(tB / total);
2372 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2374 std::string tname = "__shaderFlagsTexture";
2375 tname += normalmap_present ? "1" : "0";
2377 if (isKnownSourceImage(tname)) {
2378 return getTexture(tname);
2381 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2382 video::IImage *flags_image = driver->createImage(
2383 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2384 sanity_check(flags_image != NULL);
2385 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2386 flags_image->setPixel(0, 0, c);
2387 insertSourceImage(tname, flags_image);
2388 flags_image->drop();
2389 return getTexture(tname);
2393 const std::vector<std::string> &getTextureDirs()
2395 static thread_local std::vector<std::string> dirs =
2396 fs::GetRecursiveDirs(g_settings->get("texture_path"));