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 const std::string &texture_path = g_settings->get("texture_path");
133 if (!texture_path.empty()) {
134 std::string testpath = texture_path + DIR_DELIM + filename;
135 // Check all filename extensions. Returns "" if not found.
136 fullpath = getImagePath(testpath);
140 Check from default data directory
142 if (fullpath.empty())
144 std::string base_path = porting::path_share + DIR_DELIM + "textures"
145 + DIR_DELIM + "base" + DIR_DELIM + "pack";
146 std::string testpath = base_path + DIR_DELIM + filename;
147 // Check all filename extensions. Returns "" if not found.
148 fullpath = getImagePath(testpath);
151 // Add to cache (also an empty result is cached)
152 g_texturename_to_path_cache.set(filename, fullpath);
158 void clearTextureNameCache()
160 g_texturename_to_path_cache.clear();
164 Stores internal information about a texture.
170 video::ITexture *texture;
173 const std::string &name_,
174 video::ITexture *texture_=NULL
183 SourceImageCache: A cache used for storing source images.
186 class SourceImageCache
189 ~SourceImageCache() {
190 for (auto &m_image : m_images) {
191 m_image.second->drop();
195 void insert(const std::string &name, video::IImage *img, bool prefer_local)
197 assert(img); // Pre-condition
199 std::map<std::string, video::IImage*>::iterator n;
200 n = m_images.find(name);
201 if (n != m_images.end()){
206 video::IImage* toadd = img;
207 bool need_to_grab = true;
209 // Try to use local texture instead if asked to
211 std::string path = getTexturePath(name);
213 video::IImage *img2 = RenderingEngine::get_video_driver()->
214 createImageFromFile(path.c_str());
217 need_to_grab = false;
224 m_images[name] = toadd;
226 video::IImage* get(const std::string &name)
228 std::map<std::string, video::IImage*>::iterator n;
229 n = m_images.find(name);
230 if (n != m_images.end())
234 // Primarily fetches from cache, secondarily tries to read from filesystem
235 video::IImage *getOrLoad(const std::string &name)
237 std::map<std::string, video::IImage*>::iterator n;
238 n = m_images.find(name);
239 if (n != m_images.end()){
240 n->second->grab(); // Grab for caller
243 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
244 std::string path = getTexturePath(name);
246 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
247 <<name<<"\""<<std::endl;
250 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
252 video::IImage *img = driver->createImageFromFile(path.c_str());
255 m_images[name] = img;
256 img->grab(); // Grab for caller
261 std::map<std::string, video::IImage*> m_images;
268 class TextureSource : public IWritableTextureSource
272 virtual ~TextureSource();
276 Now, assume a texture with the id 1 exists, and has the name
277 "stone.png^mineral1".
278 Then a random thread calls getTextureId for a texture called
279 "stone.png^mineral1^crack0".
280 ...Now, WTF should happen? Well:
281 - getTextureId strips off stuff recursively from the end until
282 the remaining part is found, or nothing is left when
283 something is stripped out
285 But it is slow to search for textures by names and modify them
287 - ContentFeatures is made to contain ids for the basic plain
289 - Crack textures can be slow by themselves, but the framework
293 - Assume a texture with the id 1 exists, and has the name
294 "stone.png^mineral_coal.png".
295 - Now getNodeTile() stumbles upon a node which uses
296 texture id 1, and determines that MATERIAL_FLAG_CRACK
297 must be applied to the tile
298 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
299 has received the current crack level 0 from the client. It
300 finds out the name of the texture with getTextureName(1),
301 appends "^crack0" to it and gets a new texture id with
302 getTextureId("stone.png^mineral_coal.png^crack0").
307 Gets a texture id from cache or
308 - if main thread, generates the texture, adds to cache and returns id.
309 - if other thread, adds to request queue and waits for main thread.
311 The id 0 points to a NULL texture. It is returned in case of error.
313 u32 getTextureId(const std::string &name);
315 // Finds out the name of a cached texture.
316 std::string getTextureName(u32 id);
319 If texture specified by the name pointed by the id doesn't
320 exist, create it, then return the cached texture.
322 Can be called from any thread. If called from some other thread
323 and not found in cache, the call is queued to the main thread
326 video::ITexture* getTexture(u32 id);
328 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
331 Get a texture specifically intended for mesh
332 application, i.e. not HUD, compositing, or other 2D
333 use. This texture may be a different size and may
334 have had additional filters applied.
336 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
338 virtual Palette* getPalette(const std::string &name);
340 bool isKnownSourceImage(const std::string &name)
342 bool is_known = false;
343 bool cache_found = m_source_image_existence.get(name, &is_known);
346 // Not found in cache; find out if a local file exists
347 is_known = (!getTexturePath(name).empty());
348 m_source_image_existence.set(name, is_known);
352 // Processes queued texture requests from other threads.
353 // Shall be called from the main thread.
356 // Insert an image into the cache without touching the filesystem.
357 // Shall be called from the main thread.
358 void insertSourceImage(const std::string &name, video::IImage *img);
360 // Rebuild images and textures from the current set of source images
361 // Shall be called from the main thread.
362 void rebuildImagesAndTextures();
364 // Render a mesh to a texture.
365 // Returns NULL if render-to-texture failed.
366 // Shall be called from the main thread.
367 video::ITexture* generateTextureFromMesh(
368 const TextureFromMeshParams ¶ms);
370 video::ITexture* getNormalTexture(const std::string &name);
371 video::SColor getTextureAverageColor(const std::string &name);
372 video::ITexture *getShaderFlagsTexture(bool normamap_present);
376 // The id of the thread that is allowed to use irrlicht directly
377 std::thread::id m_main_thread;
379 // Cache of source images
380 // This should be only accessed from the main thread
381 SourceImageCache m_sourcecache;
383 // Generate a texture
384 u32 generateTexture(const std::string &name);
386 // Generate image based on a string like "stone.png" or "[crack:1:0".
387 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
388 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
390 /*! Generates an image from a full string like
391 * "stone.png^mineral_coal.png^[crack:1:0".
392 * Shall be called from the main thread.
393 * The returned Image should be dropped.
395 video::IImage* generateImage(const std::string &name);
397 // Thread-safe cache of what source images are known (true = known)
398 MutexedMap<std::string, bool> m_source_image_existence;
400 // A texture id is index in this array.
401 // The first position contains a NULL texture.
402 std::vector<TextureInfo> m_textureinfo_cache;
403 // Maps a texture name to an index in the former.
404 std::map<std::string, u32> m_name_to_id;
405 // The two former containers are behind this mutex
406 std::mutex m_textureinfo_cache_mutex;
408 // Queued texture fetches (to be processed by the main thread)
409 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
411 // Textures that have been overwritten with other ones
412 // but can't be deleted because the ITexture* might still be used
413 std::vector<video::ITexture*> m_texture_trash;
415 // Maps image file names to loaded palettes.
416 std::unordered_map<std::string, Palette> m_palettes;
418 // Cached settings needed for making textures from meshes
419 bool m_setting_trilinear_filter;
420 bool m_setting_bilinear_filter;
421 bool m_setting_anisotropic_filter;
424 IWritableTextureSource *createTextureSource()
426 return new TextureSource();
429 TextureSource::TextureSource()
431 m_main_thread = std::this_thread::get_id();
433 // Add a NULL TextureInfo as the first index, named ""
434 m_textureinfo_cache.emplace_back("");
435 m_name_to_id[""] = 0;
437 // Cache some settings
438 // Note: Since this is only done once, the game must be restarted
439 // for these settings to take effect
440 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
441 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
442 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
445 TextureSource::~TextureSource()
447 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
449 unsigned int textures_before = driver->getTextureCount();
451 for (const auto &iter : m_textureinfo_cache) {
454 driver->removeTexture(iter.texture);
456 m_textureinfo_cache.clear();
458 for (auto t : m_texture_trash) {
459 //cleanup trashed texture
460 driver->removeTexture(t);
463 infostream << "~TextureSource() "<< textures_before << "/"
464 << driver->getTextureCount() << std::endl;
467 u32 TextureSource::getTextureId(const std::string &name)
469 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
473 See if texture already exists
475 MutexAutoLock lock(m_textureinfo_cache_mutex);
476 std::map<std::string, u32>::iterator n;
477 n = m_name_to_id.find(name);
478 if (n != m_name_to_id.end())
487 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);
501 /*infostream<<"Waiting for texture from main thread, name=\""
502 <<name<<"\""<<std::endl;*/
507 // Wait result for a second
508 GetResult<std::string, u32, u8, u8>
509 result = result_queue.pop_front(1000);
511 if (result.key == name) {
516 catch(ItemNotFoundException &e)
518 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
523 infostream<<"getTextureId(): Failed"<<std::endl;
528 // Draw an image on top of an another one, using the alpha channel of the
530 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
531 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
533 // Like blit_with_alpha, but only modifies destination pixels that
535 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
536 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
538 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
539 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
540 // color alpha with the destination alpha.
541 // Otherwise, any pixels that are not fully transparent get the color alpha.
542 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
543 const video::SColor &color, int ratio, bool keep_alpha);
545 // paint a texture using the given color
546 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
547 const video::SColor &color);
549 // Apply a mask to an image
550 static void apply_mask(video::IImage *mask, video::IImage *dst,
551 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
553 // Draw or overlay a crack
554 static void draw_crack(video::IImage *crack, video::IImage *dst,
555 bool use_overlay, s32 frame_count, s32 progression,
556 video::IVideoDriver *driver);
559 void brighten(video::IImage *image);
560 // Parse a transform name
561 u32 parseImageTransform(const std::string& s);
562 // Apply transform to image dimension
563 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
564 // Apply transform to image data
565 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
568 This method generates all the textures
570 u32 TextureSource::generateTexture(const std::string &name)
572 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
574 // Empty name means texture 0
576 infostream<<"generateTexture(): name is empty"<<std::endl;
582 See if texture already exists
584 MutexAutoLock lock(m_textureinfo_cache_mutex);
585 std::map<std::string, u32>::iterator n;
586 n = m_name_to_id.find(name);
587 if (n != m_name_to_id.end()) {
593 Calling only allowed from main thread
595 if (std::this_thread::get_id() != m_main_thread) {
596 errorstream<<"TextureSource::generateTexture() "
597 "called not from main thread"<<std::endl;
601 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
602 sanity_check(driver);
604 video::IImage *img = generateImage(name);
606 video::ITexture *tex = NULL;
610 img = Align2Npot2(img, driver);
612 // Create texture from resulting image
613 tex = driver->addTexture(name.c_str(), img);
614 guiScalingCache(io::path(name.c_str()), driver, img);
619 Add texture to caches (add NULL textures too)
622 MutexAutoLock lock(m_textureinfo_cache_mutex);
624 u32 id = m_textureinfo_cache.size();
625 TextureInfo ti(name, tex);
626 m_textureinfo_cache.push_back(ti);
627 m_name_to_id[name] = id;
632 std::string TextureSource::getTextureName(u32 id)
634 MutexAutoLock lock(m_textureinfo_cache_mutex);
636 if (id >= m_textureinfo_cache.size())
638 errorstream<<"TextureSource::getTextureName(): id="<<id
639 <<" >= m_textureinfo_cache.size()="
640 <<m_textureinfo_cache.size()<<std::endl;
644 return m_textureinfo_cache[id].name;
647 video::ITexture* TextureSource::getTexture(u32 id)
649 MutexAutoLock lock(m_textureinfo_cache_mutex);
651 if (id >= m_textureinfo_cache.size())
654 return m_textureinfo_cache[id].texture;
657 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
659 u32 actual_id = getTextureId(name);
663 return getTexture(actual_id);
666 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
668 return getTexture(name + "^[applyfiltersformesh", id);
671 Palette* TextureSource::getPalette(const std::string &name)
673 // Only the main thread may load images
674 sanity_check(std::this_thread::get_id() == m_main_thread);
679 auto it = m_palettes.find(name);
680 if (it == m_palettes.end()) {
682 video::IImage *img = generateImage(name);
684 warningstream << "TextureSource::getPalette(): palette \"" << name
685 << "\" could not be loaded." << std::endl;
689 u32 w = img->getDimension().Width;
690 u32 h = img->getDimension().Height;
691 // Real area of the image
696 warningstream << "TextureSource::getPalette(): the specified"
697 << " palette image \"" << name << "\" is larger than 256"
698 << " pixels, using the first 256." << std::endl;
700 } else if (256 % area != 0)
701 warningstream << "TextureSource::getPalette(): the "
702 << "specified palette image \"" << name << "\" does not "
703 << "contain power of two pixels." << std::endl;
704 // We stretch the palette so it will fit 256 values
705 // This many param2 values will have the same color
706 u32 step = 256 / area;
707 // For each pixel in the image
708 for (u32 i = 0; i < area; i++) {
709 video::SColor c = img->getPixel(i % w, i / w);
710 // Fill in palette with 'step' colors
711 for (u32 j = 0; j < step; j++)
712 new_palette.push_back(c);
715 // Fill in remaining elements
716 while (new_palette.size() < 256)
717 new_palette.emplace_back(0xFFFFFFFF);
718 m_palettes[name] = new_palette;
719 it = m_palettes.find(name);
721 if (it != m_palettes.end())
722 return &((*it).second);
726 void TextureSource::processQueue()
731 //NOTE this is only thread safe for ONE consumer thread!
732 if (!m_get_texture_queue.empty())
734 GetRequest<std::string, u32, u8, u8>
735 request = m_get_texture_queue.pop();
737 /*infostream<<"TextureSource::processQueue(): "
738 <<"got texture request with "
739 <<"name=\""<<request.key<<"\""
742 m_get_texture_queue.pushResult(request, generateTexture(request.key));
746 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
748 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
750 sanity_check(std::this_thread::get_id() == m_main_thread);
752 m_sourcecache.insert(name, img, true);
753 m_source_image_existence.set(name, true);
756 void TextureSource::rebuildImagesAndTextures()
758 MutexAutoLock lock(m_textureinfo_cache_mutex);
760 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
761 sanity_check(driver);
764 for (TextureInfo &ti : m_textureinfo_cache) {
765 video::IImage *img = generateImage(ti.name);
767 img = Align2Npot2(img, driver);
769 // Create texture from resulting image
770 video::ITexture *t = NULL;
772 t = driver->addTexture(ti.name.c_str(), img);
773 guiScalingCache(io::path(ti.name.c_str()), driver, img);
776 video::ITexture *t_old = ti.texture;
781 m_texture_trash.push_back(t_old);
785 video::ITexture* TextureSource::generateTextureFromMesh(
786 const TextureFromMeshParams ¶ms)
788 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
789 sanity_check(driver);
792 const GLubyte* renderstr = glGetString(GL_RENDERER);
793 std::string renderer((char*) renderstr);
795 // use no render to texture hack
797 (renderer.find("Adreno") != std::string::npos) ||
798 (renderer.find("Mali") != std::string::npos) ||
799 (renderer.find("Immersion") != std::string::npos) ||
800 (renderer.find("Tegra") != std::string::npos) ||
801 g_settings->getBool("inventory_image_hack")
803 // Get a scene manager
804 scene::ISceneManager *smgr_main = m_device->getSceneManager();
805 sanity_check(smgr_main);
806 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
809 const float scaling = 0.2;
811 scene::IMeshSceneNode* meshnode =
812 smgr->addMeshSceneNode(params.mesh, NULL,
813 -1, v3f(0,0,0), v3f(0,0,0),
814 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
815 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
816 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
817 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
818 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
819 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
821 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
822 params.camera_position, params.camera_lookat);
823 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
824 camera->setProjectionMatrix(params.camera_projection_matrix, false);
826 smgr->setAmbientLight(params.ambient_light);
827 smgr->addLightSceneNode(0,
828 params.light_position,
830 params.light_radius*scaling);
832 core::dimension2d<u32> screen = driver->getScreenSize();
835 driver->beginScene(true, true, video::SColor(0,0,0,0));
836 driver->clearZBuffer();
839 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
841 irr::video::IImage* rawImage =
842 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
844 u8* pixels = static_cast<u8*>(rawImage->lock());
851 core::rect<s32> source(
852 screen.Width /2 - (screen.Width * (scaling / 2)),
853 screen.Height/2 - (screen.Height * (scaling / 2)),
854 screen.Width /2 + (screen.Width * (scaling / 2)),
855 screen.Height/2 + (screen.Height * (scaling / 2))
858 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
859 partsize.Width, partsize.Height, GL_RGBA,
860 GL_UNSIGNED_BYTE, pixels);
864 // Drop scene manager
867 unsigned int pixelcount = partsize.Width*partsize.Height;
870 for (unsigned int i=0; i < pixelcount; i++) {
888 video::IImage* inventory_image =
889 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
891 rawImage->copyToScaling(inventory_image);
894 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
896 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
897 inventory_image->drop();
900 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
904 driver->makeColorKeyTexture(rtt, v2s32(0,0));
906 if (params.delete_texture_on_shutdown)
907 m_texture_trash.push_back(rtt);
913 if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) {
914 static bool warned = false;
917 errorstream<<"TextureSource::generateTextureFromMesh(): "
918 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
924 // Create render target texture
925 video::ITexture *rtt = driver->addRenderTargetTexture(
926 params.dim, params.rtt_texture_name.c_str(),
927 video::ECF_A8R8G8B8);
930 errorstream<<"TextureSource::generateTextureFromMesh(): "
931 <<"addRenderTargetTexture returned NULL."<<std::endl;
936 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
937 driver->removeTexture(rtt);
938 errorstream<<"TextureSource::generateTextureFromMesh(): "
939 <<"failed to set render target"<<std::endl;
943 // Get a scene manager
944 scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager();
946 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
949 scene::IMeshSceneNode* meshnode =
950 smgr->addMeshSceneNode(params.mesh, NULL,
951 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
952 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
953 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
954 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
955 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
956 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
958 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
959 params.camera_position, params.camera_lookat);
960 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
961 camera->setProjectionMatrix(params.camera_projection_matrix, false);
963 smgr->setAmbientLight(params.ambient_light);
964 smgr->addLightSceneNode(0,
965 params.light_position,
967 params.light_radius);
970 driver->beginScene(true, true, video::SColor(0,0,0,0));
974 // Drop scene manager
977 // Unset render target
978 driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
980 if (params.delete_texture_on_shutdown)
981 m_texture_trash.push_back(rtt);
986 video::IImage* TextureSource::generateImage(const std::string &name)
988 // Get the base image
990 const char separator = '^';
991 const char escape = '\\';
992 const char paren_open = '(';
993 const char paren_close = ')';
995 // Find last separator in the name
996 s32 last_separator_pos = -1;
998 for (s32 i = name.size() - 1; i >= 0; i--) {
999 if (i > 0 && name[i-1] == escape)
1003 if (paren_bal == 0) {
1004 last_separator_pos = i;
1005 i = -1; // break out of loop
1009 if (paren_bal == 0) {
1010 errorstream << "generateImage(): unbalanced parentheses"
1011 << "(extranous '(') while generating texture \""
1012 << name << "\"" << std::endl;
1024 if (paren_bal > 0) {
1025 errorstream << "generateImage(): unbalanced parentheses"
1026 << "(missing matching '(') while generating texture \""
1027 << name << "\"" << std::endl;
1032 video::IImage *baseimg = NULL;
1035 If separator was found, make the base image
1036 using a recursive call.
1038 if (last_separator_pos != -1) {
1039 baseimg = generateImage(name.substr(0, last_separator_pos));
1043 Parse out the last part of the name of the image and act
1047 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1050 If this name is enclosed in parentheses, generate it
1051 and blit it onto the base image
1053 if (last_part_of_name[0] == paren_open
1054 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1055 std::string name2 = last_part_of_name.substr(1,
1056 last_part_of_name.size() - 2);
1057 video::IImage *tmp = generateImage(name2);
1059 errorstream << "generateImage(): "
1060 "Failed to generate \"" << name2 << "\""
1064 core::dimension2d<u32> dim = tmp->getDimension();
1066 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1071 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1072 // Generate image according to part of name
1073 errorstream << "generateImage(): "
1074 "Failed to generate \"" << last_part_of_name << "\""
1078 // If no resulting image, print a warning
1079 if (baseimg == NULL) {
1080 errorstream << "generateImage(): baseimg is NULL (attempted to"
1081 " create texture \"" << name << "\")" << std::endl;
1088 #include <GLES/gl.h>
1090 * Check and align image to npot2 if required by hardware
1091 * @param image image to check for npot2 alignment
1092 * @param driver driver to use for image operations
1093 * @return image or copy of image aligned to npot2
1096 inline u16 get_GL_major_version()
1098 const GLubyte *gl_version = glGetString(GL_VERSION);
1099 return (u16) (gl_version[0] - '0');
1102 video::IImage * Align2Npot2(video::IImage * image,
1103 video::IVideoDriver* driver)
1105 if (image == NULL) {
1109 core::dimension2d<u32> dim = image->getDimension();
1111 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1113 // Only GLES2 is trusted to correctly report npot support
1114 if (get_GL_major_version() > 1 &&
1115 extensions.find("GL_OES_texture_npot") != std::string::npos) {
1119 unsigned int height = npot2(dim.Height);
1120 unsigned int width = npot2(dim.Width);
1122 if ((dim.Height == height) &&
1123 (dim.Width == width)) {
1127 if (dim.Height > height) {
1131 if (dim.Width > width) {
1135 video::IImage *targetimage =
1136 driver->createImage(video::ECF_A8R8G8B8,
1137 core::dimension2d<u32>(width, height));
1139 if (targetimage != NULL) {
1140 image->copyToScaling(targetimage);
1148 static std::string unescape_string(const std::string &str, const char esc = '\\')
1151 size_t pos = 0, cpos;
1152 out.reserve(str.size());
1154 cpos = str.find_first_of(esc, pos);
1155 if (cpos == std::string::npos) {
1156 out += str.substr(pos);
1159 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1165 bool TextureSource::generateImagePart(std::string part_of_name,
1166 video::IImage *& baseimg)
1168 const char escape = '\\'; // same as in generateImage()
1169 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1170 sanity_check(driver);
1172 // Stuff starting with [ are special commands
1173 if (part_of_name.empty() || part_of_name[0] != '[') {
1174 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1176 image = Align2Npot2(image, driver);
1178 if (image == NULL) {
1179 if (!part_of_name.empty()) {
1181 // Do not create normalmap dummies
1182 if (part_of_name.find("_normal.png") != std::string::npos) {
1183 warningstream << "generateImage(): Could not load normal map \""
1184 << part_of_name << "\"" << std::endl;
1188 errorstream << "generateImage(): Could not load image \""
1189 << part_of_name << "\" while building texture; "
1190 "Creating a dummy image" << std::endl;
1193 // Just create a dummy image
1194 //core::dimension2d<u32> dim(2,2);
1195 core::dimension2d<u32> dim(1,1);
1196 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1197 sanity_check(image != NULL);
1198 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1199 image->setPixel(1,0, video::SColor(255,0,255,0));
1200 image->setPixel(0,1, video::SColor(255,0,0,255));
1201 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1202 image->setPixel(0,0, video::SColor(255,myrand()%256,
1203 myrand()%256,myrand()%256));
1204 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1205 myrand()%256,myrand()%256));
1206 image->setPixel(0,1, video::SColor(255,myrand()%256,
1207 myrand()%256,myrand()%256));
1208 image->setPixel(1,1, video::SColor(255,myrand()%256,
1209 myrand()%256,myrand()%256));*/
1212 // If base image is NULL, load as base.
1213 if (baseimg == NULL)
1215 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1217 Copy it this way to get an alpha channel.
1218 Otherwise images with alpha cannot be blitted on
1219 images that don't have alpha in the original file.
1221 core::dimension2d<u32> dim = image->getDimension();
1222 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1223 image->copyTo(baseimg);
1225 // Else blit on base.
1228 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1229 // Size of the copied area
1230 core::dimension2d<u32> dim = image->getDimension();
1231 //core::dimension2d<u32> dim(16,16);
1232 // Position to copy the blitted to in the base image
1233 core::position2d<s32> pos_to(0,0);
1234 // Position to copy the blitted from in the blitted image
1235 core::position2d<s32> pos_from(0,0);
1237 /*image->copyToWithAlpha(baseimg, pos_to,
1238 core::rect<s32>(pos_from, dim),
1239 video::SColor(255,255,255,255),
1242 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1243 if (dim == dim_dst) {
1244 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1245 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1246 // Upscale overlying image
1247 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1248 createImage(video::ECF_A8R8G8B8, dim_dst);
1249 image->copyToScaling(scaled_image);
1251 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1252 scaled_image->drop();
1254 // Upscale base image
1255 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1256 createImage(video::ECF_A8R8G8B8, dim);
1257 baseimg->copyToScaling(scaled_base);
1259 baseimg = scaled_base;
1261 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1269 // A special texture modification
1271 /*infostream<<"generateImage(): generating special "
1272 <<"modification \""<<part_of_name<<"\""
1278 Adds a cracking texture
1279 N = animation frame count, P = crack progression
1281 if (str_starts_with(part_of_name, "[crack"))
1283 if (baseimg == NULL) {
1284 errorstream<<"generateImagePart(): baseimg == NULL "
1285 <<"for part_of_name=\""<<part_of_name
1286 <<"\", cancelling."<<std::endl;
1290 // Crack image number and overlay option
1291 bool use_overlay = (part_of_name[6] == 'o');
1292 Strfnd sf(part_of_name);
1294 s32 frame_count = stoi(sf.next(":"));
1295 s32 progression = stoi(sf.next(":"));
1297 if (progression >= 0) {
1301 It is an image with a number of cracking stages
1304 video::IImage *img_crack = m_sourcecache.getOrLoad(
1305 "crack_anylength.png");
1308 draw_crack(img_crack, baseimg,
1309 use_overlay, frame_count,
1310 progression, driver);
1316 [combine:WxH:X,Y=filename:X,Y=filename2
1317 Creates a bigger texture from any amount of smaller ones
1319 else if (str_starts_with(part_of_name, "[combine"))
1321 Strfnd sf(part_of_name);
1323 u32 w0 = stoi(sf.next("x"));
1324 u32 h0 = stoi(sf.next(":"));
1325 core::dimension2d<u32> dim(w0,h0);
1326 if (baseimg == NULL) {
1327 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1328 baseimg->fill(video::SColor(0,0,0,0));
1330 while (!sf.at_end()) {
1331 u32 x = stoi(sf.next(","));
1332 u32 y = stoi(sf.next("="));
1333 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1334 infostream<<"Adding \""<<filename
1335 <<"\" to combined ("<<x<<","<<y<<")"
1337 video::IImage *img = generateImage(filename);
1339 core::dimension2d<u32> dim = img->getDimension();
1340 infostream<<"Size "<<dim.Width
1341 <<"x"<<dim.Height<<std::endl;
1342 core::position2d<s32> pos_base(x, y);
1343 video::IImage *img2 =
1344 driver->createImage(video::ECF_A8R8G8B8, dim);
1347 /*img2->copyToWithAlpha(baseimg, pos_base,
1348 core::rect<s32>(v2s32(0,0), dim),
1349 video::SColor(255,255,255,255),
1351 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1354 errorstream << "generateImagePart(): Failed to load image \""
1355 << filename << "\" for [combine" << std::endl;
1362 else if (str_starts_with(part_of_name, "[brighten"))
1364 if (baseimg == NULL) {
1365 errorstream<<"generateImagePart(): baseimg==NULL "
1366 <<"for part_of_name=\""<<part_of_name
1367 <<"\", cancelling."<<std::endl;
1375 Make image completely opaque.
1376 Used for the leaves texture when in old leaves mode, so
1377 that the transparent parts don't look completely black
1378 when simple alpha channel is used for rendering.
1380 else if (str_starts_with(part_of_name, "[noalpha"))
1382 if (baseimg == NULL){
1383 errorstream<<"generateImagePart(): baseimg==NULL "
1384 <<"for part_of_name=\""<<part_of_name
1385 <<"\", cancelling."<<std::endl;
1389 core::dimension2d<u32> dim = baseimg->getDimension();
1391 // Set alpha to full
1392 for (u32 y=0; y<dim.Height; y++)
1393 for (u32 x=0; x<dim.Width; x++)
1395 video::SColor c = baseimg->getPixel(x,y);
1397 baseimg->setPixel(x,y,c);
1402 Convert one color to transparent.
1404 else if (str_starts_with(part_of_name, "[makealpha:"))
1406 if (baseimg == NULL) {
1407 errorstream<<"generateImagePart(): baseimg == NULL "
1408 <<"for part_of_name=\""<<part_of_name
1409 <<"\", cancelling."<<std::endl;
1413 Strfnd sf(part_of_name.substr(11));
1414 u32 r1 = stoi(sf.next(","));
1415 u32 g1 = stoi(sf.next(","));
1416 u32 b1 = stoi(sf.next(""));
1418 core::dimension2d<u32> dim = baseimg->getDimension();
1420 /*video::IImage *oldbaseimg = baseimg;
1421 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1422 oldbaseimg->copyTo(baseimg);
1423 oldbaseimg->drop();*/
1425 // Set alpha to full
1426 for (u32 y=0; y<dim.Height; y++)
1427 for (u32 x=0; x<dim.Width; x++)
1429 video::SColor c = baseimg->getPixel(x,y);
1431 u32 g = c.getGreen();
1432 u32 b = c.getBlue();
1433 if (!(r == r1 && g == g1 && b == b1))
1436 baseimg->setPixel(x,y,c);
1441 Rotates and/or flips the image.
1443 N can be a number (between 0 and 7) or a transform name.
1444 Rotations are counter-clockwise.
1446 1 R90 rotate by 90 degrees
1447 2 R180 rotate by 180 degrees
1448 3 R270 rotate by 270 degrees
1450 5 FXR90 flip X then rotate by 90 degrees
1452 7 FYR90 flip Y then rotate by 90 degrees
1454 Note: Transform names can be concatenated to produce
1455 their product (applies the first then the second).
1456 The resulting transform will be equivalent to one of the
1457 eight existing ones, though (see: dihedral group).
1459 else if (str_starts_with(part_of_name, "[transform"))
1461 if (baseimg == NULL) {
1462 errorstream<<"generateImagePart(): baseimg == NULL "
1463 <<"for part_of_name=\""<<part_of_name
1464 <<"\", cancelling."<<std::endl;
1468 u32 transform = parseImageTransform(part_of_name.substr(10));
1469 core::dimension2d<u32> dim = imageTransformDimension(
1470 transform, baseimg->getDimension());
1471 video::IImage *image = driver->createImage(
1472 baseimg->getColorFormat(), dim);
1473 sanity_check(image != NULL);
1474 imageTransform(transform, baseimg, image);
1479 [inventorycube{topimage{leftimage{rightimage
1480 In every subimage, replace ^ with &.
1481 Create an "inventory cube".
1482 NOTE: This should be used only on its own.
1483 Example (a grass block (not actually used in game):
1484 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1486 else if (str_starts_with(part_of_name, "[inventorycube"))
1488 if (baseimg != NULL){
1489 errorstream<<"generateImagePart(): baseimg != NULL "
1490 <<"for part_of_name=\""<<part_of_name
1491 <<"\", cancelling."<<std::endl;
1495 str_replace(part_of_name, '&', '^');
1496 Strfnd sf(part_of_name);
1498 std::string imagename_top = sf.next("{");
1499 std::string imagename_left = sf.next("{");
1500 std::string imagename_right = sf.next("{");
1502 // Generate images for the faces of the cube
1503 video::IImage *img_top = generateImage(imagename_top);
1504 video::IImage *img_left = generateImage(imagename_left);
1505 video::IImage *img_right = generateImage(imagename_right);
1507 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1508 errorstream << "generateImagePart(): Failed to create textures"
1509 << " for inventorycube \"" << part_of_name << "\""
1511 baseimg = generateImage(imagename_top);
1516 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1517 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1519 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1520 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1522 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1523 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1526 // Create textures from images
1527 video::ITexture *texture_top = driver->addTexture(
1528 (imagename_top + "__temp__").c_str(), img_top);
1529 video::ITexture *texture_left = driver->addTexture(
1530 (imagename_left + "__temp__").c_str(), img_left);
1531 video::ITexture *texture_right = driver->addTexture(
1532 (imagename_right + "__temp__").c_str(), img_right);
1533 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1541 Draw a cube mesh into a render target texture
1543 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1544 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1545 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1546 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1547 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1548 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1549 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1550 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1552 TextureFromMeshParams params;
1554 params.dim.set(64, 64);
1555 params.rtt_texture_name = part_of_name + "_RTT";
1556 // We will delete the rtt texture ourselves
1557 params.delete_texture_on_shutdown = false;
1558 params.camera_position.set(0, 1.0, -1.5);
1559 params.camera_position.rotateXZBy(45);
1560 params.camera_lookat.set(0, 0, 0);
1561 // Set orthogonal projection
1562 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1563 1.65, 1.65, 0, 100);
1565 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1566 params.light_position.set(10, 100, -50);
1567 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1568 params.light_radius = 1000;
1570 video::ITexture *rtt = generateTextureFromMesh(params);
1576 driver->removeTexture(texture_top);
1577 driver->removeTexture(texture_left);
1578 driver->removeTexture(texture_right);
1581 baseimg = generateImage(imagename_top);
1585 // Create image of render target
1586 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1587 FATAL_ERROR_IF(!image, "Could not create image of render target");
1590 driver->removeTexture(rtt);
1592 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1595 image->copyTo(baseimg);
1600 [lowpart:percent:filename
1601 Adds the lower part of a texture
1603 else if (str_starts_with(part_of_name, "[lowpart:"))
1605 Strfnd sf(part_of_name);
1607 u32 percent = stoi(sf.next(":"));
1608 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1610 if (baseimg == NULL)
1611 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1612 video::IImage *img = generateImage(filename);
1615 core::dimension2d<u32> dim = img->getDimension();
1616 core::position2d<s32> pos_base(0, 0);
1617 video::IImage *img2 =
1618 driver->createImage(video::ECF_A8R8G8B8, dim);
1621 core::position2d<s32> clippos(0, 0);
1622 clippos.Y = dim.Height * (100-percent) / 100;
1623 core::dimension2d<u32> clipdim = dim;
1624 clipdim.Height = clipdim.Height * percent / 100 + 1;
1625 core::rect<s32> cliprect(clippos, clipdim);
1626 img2->copyToWithAlpha(baseimg, pos_base,
1627 core::rect<s32>(v2s32(0,0), dim),
1628 video::SColor(255,255,255,255),
1635 Crops a frame of a vertical animation.
1636 N = frame count, I = frame index
1638 else if (str_starts_with(part_of_name, "[verticalframe:"))
1640 Strfnd sf(part_of_name);
1642 u32 frame_count = stoi(sf.next(":"));
1643 u32 frame_index = stoi(sf.next(":"));
1645 if (baseimg == NULL){
1646 errorstream<<"generateImagePart(): baseimg != NULL "
1647 <<"for part_of_name=\""<<part_of_name
1648 <<"\", cancelling."<<std::endl;
1652 v2u32 frame_size = baseimg->getDimension();
1653 frame_size.Y /= frame_count;
1655 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1658 errorstream<<"generateImagePart(): Could not create image "
1659 <<"for part_of_name=\""<<part_of_name
1660 <<"\", cancelling."<<std::endl;
1664 // Fill target image with transparency
1665 img->fill(video::SColor(0,0,0,0));
1667 core::dimension2d<u32> dim = frame_size;
1668 core::position2d<s32> pos_dst(0, 0);
1669 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1670 baseimg->copyToWithAlpha(img, pos_dst,
1671 core::rect<s32>(pos_src, dim),
1672 video::SColor(255,255,255,255),
1680 Applies a mask to an image
1682 else if (str_starts_with(part_of_name, "[mask:"))
1684 if (baseimg == NULL) {
1685 errorstream << "generateImage(): baseimg == NULL "
1686 << "for part_of_name=\"" << part_of_name
1687 << "\", cancelling." << std::endl;
1690 Strfnd sf(part_of_name);
1692 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1694 video::IImage *img = generateImage(filename);
1696 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1697 img->getDimension());
1700 errorstream << "generateImage(): Failed to load \""
1701 << filename << "\".";
1706 multiplys a given color to any pixel of an image
1707 color = color as ColorString
1709 else if (str_starts_with(part_of_name, "[multiply:")) {
1710 Strfnd sf(part_of_name);
1712 std::string color_str = sf.next(":");
1714 if (baseimg == NULL) {
1715 errorstream << "generateImagePart(): baseimg != NULL "
1716 << "for part_of_name=\"" << part_of_name
1717 << "\", cancelling." << std::endl;
1721 video::SColor color;
1723 if (!parseColorString(color_str, color, false))
1726 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1730 Overlays image with given color
1731 color = color as ColorString
1733 else if (str_starts_with(part_of_name, "[colorize:"))
1735 Strfnd sf(part_of_name);
1737 std::string color_str = sf.next(":");
1738 std::string ratio_str = sf.next(":");
1740 if (baseimg == NULL) {
1741 errorstream << "generateImagePart(): baseimg != NULL "
1742 << "for part_of_name=\"" << part_of_name
1743 << "\", cancelling." << std::endl;
1747 video::SColor color;
1749 bool keep_alpha = false;
1751 if (!parseColorString(color_str, color, false))
1754 if (is_number(ratio_str))
1755 ratio = mystoi(ratio_str, 0, 255);
1756 else if (ratio_str == "alpha")
1759 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1762 [applyfiltersformesh
1765 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1767 // Apply the "clean transparent" filter, if configured.
1768 if (g_settings->getBool("texture_clean_transparent"))
1769 imageCleanTransparent(baseimg, 127);
1771 /* Upscale textures to user's requested minimum size. This is a trick to make
1772 * filters look as good on low-res textures as on high-res ones, by making
1773 * low-res textures BECOME high-res ones. This is helpful for worlds that
1774 * mix high- and low-res textures, or for mods with least-common-denominator
1775 * textures that don't have the resources to offer high-res alternatives.
1777 s32 scaleto = g_settings->getS32("texture_min_size");
1779 const core::dimension2d<u32> dim = baseimg->getDimension();
1781 /* Calculate scaling needed to make the shortest texture dimension
1782 * equal to the target minimum. If e.g. this is a vertical frames
1783 * animation, the short dimension will be the real size.
1785 if ((dim.Width == 0) || (dim.Height == 0)) {
1786 errorstream << "generateImagePart(): Illegal 0 dimension "
1787 << "for part_of_name=\""<< part_of_name
1788 << "\", cancelling." << std::endl;
1791 u32 xscale = scaleto / dim.Width;
1792 u32 yscale = scaleto / dim.Height;
1793 u32 scale = (xscale > yscale) ? xscale : yscale;
1795 // Never downscale; only scale up by 2x or more.
1797 u32 w = scale * dim.Width;
1798 u32 h = scale * dim.Height;
1799 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1800 video::IImage *newimg = driver->createImage(
1801 baseimg->getColorFormat(), newdim);
1802 baseimg->copyToScaling(newimg);
1810 Resizes the base image to the given dimensions
1812 else if (str_starts_with(part_of_name, "[resize"))
1814 if (baseimg == NULL) {
1815 errorstream << "generateImagePart(): baseimg == NULL "
1816 << "for part_of_name=\""<< part_of_name
1817 << "\", cancelling." << std::endl;
1821 Strfnd sf(part_of_name);
1823 u32 width = stoi(sf.next("x"));
1824 u32 height = stoi(sf.next(""));
1825 core::dimension2d<u32> dim(width, height);
1827 video::IImage *image = RenderingEngine::get_video_driver()->
1828 createImage(video::ECF_A8R8G8B8, dim);
1829 baseimg->copyToScaling(image);
1835 Makes the base image transparent according to the given ratio.
1836 R must be between 0 and 255.
1837 0 means totally transparent.
1838 255 means totally opaque.
1840 else if (str_starts_with(part_of_name, "[opacity:")) {
1841 if (baseimg == NULL) {
1842 errorstream << "generateImagePart(): baseimg == NULL "
1843 << "for part_of_name=\"" << part_of_name
1844 << "\", cancelling." << std::endl;
1848 Strfnd sf(part_of_name);
1851 u32 ratio = mystoi(sf.next(""), 0, 255);
1853 core::dimension2d<u32> dim = baseimg->getDimension();
1855 for (u32 y = 0; y < dim.Height; y++)
1856 for (u32 x = 0; x < dim.Width; x++)
1858 video::SColor c = baseimg->getPixel(x, y);
1859 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1860 baseimg->setPixel(x, y, c);
1865 Inverts the given channels of the base image.
1866 Mode may contain the characters "r", "g", "b", "a".
1867 Only the channels that are mentioned in the mode string
1870 else if (str_starts_with(part_of_name, "[invert:")) {
1871 if (baseimg == NULL) {
1872 errorstream << "generateImagePart(): baseimg == NULL "
1873 << "for part_of_name=\"" << part_of_name
1874 << "\", cancelling." << std::endl;
1878 Strfnd sf(part_of_name);
1881 std::string mode = sf.next("");
1883 if (mode.find('a') != std::string::npos)
1884 mask |= 0xff000000UL;
1885 if (mode.find('r') != std::string::npos)
1886 mask |= 0x00ff0000UL;
1887 if (mode.find('g') != std::string::npos)
1888 mask |= 0x0000ff00UL;
1889 if (mode.find('b') != std::string::npos)
1890 mask |= 0x000000ffUL;
1892 core::dimension2d<u32> dim = baseimg->getDimension();
1894 for (u32 y = 0; y < dim.Height; y++)
1895 for (u32 x = 0; x < dim.Width; x++)
1897 video::SColor c = baseimg->getPixel(x, y);
1899 baseimg->setPixel(x, y, c);
1904 Retrieves a tile at position X,Y (in tiles)
1905 from the base image it assumes to be a
1906 tilesheet with dimensions W,H (in tiles).
1908 else if (part_of_name.substr(0,7) == "[sheet:") {
1909 if (baseimg == NULL) {
1910 errorstream << "generateImagePart(): baseimg != NULL "
1911 << "for part_of_name=\"" << part_of_name
1912 << "\", cancelling." << std::endl;
1916 Strfnd sf(part_of_name);
1918 u32 w0 = stoi(sf.next("x"));
1919 u32 h0 = stoi(sf.next(":"));
1920 u32 x0 = stoi(sf.next(","));
1921 u32 y0 = stoi(sf.next(":"));
1923 core::dimension2d<u32> img_dim = baseimg->getDimension();
1924 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1926 video::IImage *img = driver->createImage(
1927 video::ECF_A8R8G8B8, tile_dim);
1929 errorstream << "generateImagePart(): Could not create image "
1930 << "for part_of_name=\"" << part_of_name
1931 << "\", cancelling." << std::endl;
1935 img->fill(video::SColor(0,0,0,0));
1936 v2u32 vdim(tile_dim);
1937 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1938 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1939 video::SColor(255,255,255,255), NULL);
1947 errorstream << "generateImagePart(): Invalid "
1948 " modification: \"" << part_of_name << "\"" << std::endl;
1956 Draw an image on top of an another one, using the alpha channel of the
1959 This exists because IImage::copyToWithAlpha() doesn't seem to always
1962 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1963 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1965 for (u32 y0=0; y0<size.Y; y0++)
1966 for (u32 x0=0; x0<size.X; x0++)
1968 s32 src_x = src_pos.X + x0;
1969 s32 src_y = src_pos.Y + y0;
1970 s32 dst_x = dst_pos.X + x0;
1971 s32 dst_y = dst_pos.Y + y0;
1972 video::SColor src_c = src->getPixel(src_x, src_y);
1973 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1974 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1975 dst->setPixel(dst_x, dst_y, dst_c);
1980 Draw an image on top of an another one, using the alpha channel of the
1981 source image; only modify fully opaque pixels in destinaion
1983 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1984 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1986 for (u32 y0=0; y0<size.Y; y0++)
1987 for (u32 x0=0; x0<size.X; x0++)
1989 s32 src_x = src_pos.X + x0;
1990 s32 src_y = src_pos.Y + y0;
1991 s32 dst_x = dst_pos.X + x0;
1992 s32 dst_y = dst_pos.Y + y0;
1993 video::SColor src_c = src->getPixel(src_x, src_y);
1994 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1995 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1997 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1998 dst->setPixel(dst_x, dst_y, dst_c);
2003 // This function has been disabled because it is currently unused.
2004 // Feel free to re-enable if you find it handy.
2007 Draw an image on top of an another one, using the specified ratio
2008 modify all partially-opaque pixels in the destination.
2010 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2011 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2013 for (u32 y0 = 0; y0 < size.Y; y0++)
2014 for (u32 x0 = 0; x0 < size.X; x0++)
2016 s32 src_x = src_pos.X + x0;
2017 s32 src_y = src_pos.Y + y0;
2018 s32 dst_x = dst_pos.X + x0;
2019 s32 dst_y = dst_pos.Y + y0;
2020 video::SColor src_c = src->getPixel(src_x, src_y);
2021 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2022 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2025 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2027 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2028 dst->setPixel(dst_x, dst_y, dst_c);
2035 Apply color to destination
2037 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2038 const video::SColor &color, int ratio, bool keep_alpha)
2040 u32 alpha = color.getAlpha();
2041 video::SColor dst_c;
2042 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2043 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2045 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2046 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2047 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2048 if (dst_alpha > 0) {
2049 dst_c.setAlpha(dst_alpha * alpha / 255);
2050 dst->setPixel(x, y, dst_c);
2053 } else { // replace the color including the alpha
2054 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2055 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2056 if (dst->getPixel(x, y).getAlpha() > 0)
2057 dst->setPixel(x, y, color);
2059 } else { // interpolate between the color and destination
2060 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2061 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2062 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2063 dst_c = dst->getPixel(x, y);
2064 if (dst_c.getAlpha() > 0) {
2065 dst_c = color.getInterpolated(dst_c, interp);
2066 dst->setPixel(x, y, dst_c);
2073 Apply color to destination
2075 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2076 const video::SColor &color)
2078 video::SColor dst_c;
2080 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2081 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2082 dst_c = dst->getPixel(x, y);
2085 (dst_c.getRed() * color.getRed()) / 255,
2086 (dst_c.getGreen() * color.getGreen()) / 255,
2087 (dst_c.getBlue() * color.getBlue()) / 255
2089 dst->setPixel(x, y, dst_c);
2094 Apply mask to destination
2096 static void apply_mask(video::IImage *mask, video::IImage *dst,
2097 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2099 for (u32 y0 = 0; y0 < size.Y; y0++) {
2100 for (u32 x0 = 0; x0 < size.X; x0++) {
2101 s32 mask_x = x0 + mask_pos.X;
2102 s32 mask_y = y0 + mask_pos.Y;
2103 s32 dst_x = x0 + dst_pos.X;
2104 s32 dst_y = y0 + dst_pos.Y;
2105 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2106 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2107 dst_c.color &= mask_c.color;
2108 dst->setPixel(dst_x, dst_y, dst_c);
2113 static void draw_crack(video::IImage *crack, video::IImage *dst,
2114 bool use_overlay, s32 frame_count, s32 progression,
2115 video::IVideoDriver *driver)
2117 // Dimension of destination image
2118 core::dimension2d<u32> dim_dst = dst->getDimension();
2119 // Dimension of original image
2120 core::dimension2d<u32> dim_crack = crack->getDimension();
2121 // Count of crack stages
2122 s32 crack_count = dim_crack.Height / dim_crack.Width;
2123 // Limit frame_count
2124 if (frame_count > (s32) dim_dst.Height)
2125 frame_count = dim_dst.Height;
2126 if (frame_count < 1)
2128 // Limit progression
2129 if (progression > crack_count-1)
2130 progression = crack_count-1;
2131 // Dimension of a single crack stage
2132 core::dimension2d<u32> dim_crack_cropped(
2136 // Dimension of the scaled crack stage,
2137 // which is the same as the dimension of a single destination frame
2138 core::dimension2d<u32> dim_crack_scaled(
2140 dim_dst.Height / frame_count
2142 // Create cropped and scaled crack images
2143 video::IImage *crack_cropped = driver->createImage(
2144 video::ECF_A8R8G8B8, dim_crack_cropped);
2145 video::IImage *crack_scaled = driver->createImage(
2146 video::ECF_A8R8G8B8, dim_crack_scaled);
2148 if (crack_cropped && crack_scaled)
2151 v2s32 pos_crack(0, progression*dim_crack.Width);
2152 crack->copyTo(crack_cropped,
2154 core::rect<s32>(pos_crack, dim_crack_cropped));
2155 // Scale crack image by copying
2156 crack_cropped->copyToScaling(crack_scaled);
2157 // Copy or overlay crack image onto each frame
2158 for (s32 i = 0; i < frame_count; ++i)
2160 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2163 blit_with_alpha_overlay(crack_scaled, dst,
2164 v2s32(0,0), dst_pos,
2169 blit_with_alpha(crack_scaled, dst,
2170 v2s32(0,0), dst_pos,
2177 crack_scaled->drop();
2180 crack_cropped->drop();
2183 void brighten(video::IImage *image)
2188 core::dimension2d<u32> dim = image->getDimension();
2190 for (u32 y=0; y<dim.Height; y++)
2191 for (u32 x=0; x<dim.Width; x++)
2193 video::SColor c = image->getPixel(x,y);
2194 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2195 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2196 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2197 image->setPixel(x,y,c);
2201 u32 parseImageTransform(const std::string& s)
2203 int total_transform = 0;
2205 std::string transform_names[8];
2206 transform_names[0] = "i";
2207 transform_names[1] = "r90";
2208 transform_names[2] = "r180";
2209 transform_names[3] = "r270";
2210 transform_names[4] = "fx";
2211 transform_names[6] = "fy";
2213 std::size_t pos = 0;
2214 while(pos < s.size())
2217 for (int i = 0; i <= 7; ++i)
2219 const std::string &name_i = transform_names[i];
2221 if (s[pos] == ('0' + i))
2228 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2230 pos += name_i.size();
2237 // Multiply total_transform and transform in the group D4
2240 new_total = (transform + total_transform) % 4;
2242 new_total = (transform - total_transform + 8) % 4;
2243 if ((transform >= 4) ^ (total_transform >= 4))
2246 total_transform = new_total;
2248 return total_transform;
2251 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2253 if (transform % 2 == 0)
2256 return core::dimension2d<u32>(dim.Height, dim.Width);
2259 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2261 if (src == NULL || dst == NULL)
2264 core::dimension2d<u32> dstdim = dst->getDimension();
2267 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2268 assert(transform <= 7);
2271 Compute the transformation from source coordinates (sx,sy)
2272 to destination coordinates (dx,dy).
2276 if (transform == 0) // identity
2277 sxn = 0, syn = 2; // sx = dx, sy = dy
2278 else if (transform == 1) // rotate by 90 degrees ccw
2279 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2280 else if (transform == 2) // rotate by 180 degrees
2281 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2282 else if (transform == 3) // rotate by 270 degrees ccw
2283 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2284 else if (transform == 4) // flip x
2285 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2286 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2287 sxn = 2, syn = 0; // sx = dy, sy = dx
2288 else if (transform == 6) // flip y
2289 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2290 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2291 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2293 for (u32 dy=0; dy<dstdim.Height; dy++)
2294 for (u32 dx=0; dx<dstdim.Width; dx++)
2296 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2297 u32 sx = entries[sxn];
2298 u32 sy = entries[syn];
2299 video::SColor c = src->getPixel(sx,sy);
2300 dst->setPixel(dx,dy,c);
2304 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2306 if (isKnownSourceImage("override_normal.png"))
2307 return getTexture("override_normal.png");
2308 std::string fname_base = name;
2309 static const char *normal_ext = "_normal.png";
2310 static const u32 normal_ext_size = strlen(normal_ext);
2311 size_t pos = fname_base.find('.');
2312 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2313 if (isKnownSourceImage(fname_normal)) {
2314 // look for image extension and replace it
2316 while ((i = fname_base.find('.', i)) != std::string::npos) {
2317 fname_base.replace(i, 4, normal_ext);
2318 i += normal_ext_size;
2320 return getTexture(fname_base);
2325 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2327 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2328 video::SColor c(0, 0, 0, 0);
2329 video::ITexture *texture = getTexture(name);
2330 video::IImage *image = driver->createImage(texture,
2331 core::position2d<s32>(0, 0),
2332 texture->getOriginalSize());
2337 core::dimension2d<u32> dim = image->getDimension();
2340 step = dim.Width / 16;
2341 for (u16 x = 0; x < dim.Width; x += step) {
2342 for (u16 y = 0; y < dim.Width; y += step) {
2343 c = image->getPixel(x,y);
2344 if (c.getAlpha() > 0) {
2354 c.setRed(tR / total);
2355 c.setGreen(tG / total);
2356 c.setBlue(tB / total);
2363 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2365 std::string tname = "__shaderFlagsTexture";
2366 tname += normalmap_present ? "1" : "0";
2368 if (isKnownSourceImage(tname)) {
2369 return getTexture(tname);
2372 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2373 video::IImage *flags_image = driver->createImage(
2374 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2375 sanity_check(flags_image != NULL);
2376 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2377 flags_image->setPixel(0, 0, c);
2378 insertSourceImage(tname, flags_image);
2379 flags_image->drop();
2380 return getTexture(tname);