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) {
488 return generateTexture(name);
492 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
494 // We're gonna ask the result to be put into here
495 static ResultQueue<std::string, u32, u8, u8> result_queue;
497 // Throw a request in
498 m_get_texture_queue.add(name, 0, 0, &result_queue);
502 // Wait result for a second
503 GetResult<std::string, u32, u8, u8>
504 result = result_queue.pop_front(1000);
506 if (result.key == name) {
510 } catch(ItemNotFoundException &e) {
511 errorstream << "Waiting for texture " << name << " timed out." << std::endl;
515 infostream << "getTextureId(): Failed" << std::endl;
520 // Draw an image on top of an another one, using the alpha channel of the
522 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
523 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
525 // Like blit_with_alpha, but only modifies destination pixels that
527 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
528 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
530 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
531 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
532 // color alpha with the destination alpha.
533 // Otherwise, any pixels that are not fully transparent get the color alpha.
534 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
535 const video::SColor &color, int ratio, bool keep_alpha);
537 // paint a texture using the given color
538 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
539 const video::SColor &color);
541 // Apply a mask to an image
542 static void apply_mask(video::IImage *mask, video::IImage *dst,
543 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
545 // Draw or overlay a crack
546 static void draw_crack(video::IImage *crack, video::IImage *dst,
547 bool use_overlay, s32 frame_count, s32 progression,
548 video::IVideoDriver *driver, u8 tiles = 1);
551 void brighten(video::IImage *image);
552 // Parse a transform name
553 u32 parseImageTransform(const std::string& s);
554 // Apply transform to image dimension
555 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
556 // Apply transform to image data
557 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
560 This method generates all the textures
562 u32 TextureSource::generateTexture(const std::string &name)
564 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
566 // Empty name means texture 0
568 infostream<<"generateTexture(): name is empty"<<std::endl;
574 See if texture already exists
576 MutexAutoLock lock(m_textureinfo_cache_mutex);
577 std::map<std::string, u32>::iterator n;
578 n = m_name_to_id.find(name);
579 if (n != m_name_to_id.end()) {
585 Calling only allowed from main thread
587 if (std::this_thread::get_id() != m_main_thread) {
588 errorstream<<"TextureSource::generateTexture() "
589 "called not from main thread"<<std::endl;
593 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
594 sanity_check(driver);
596 video::IImage *img = generateImage(name);
598 video::ITexture *tex = NULL;
602 img = Align2Npot2(img, driver);
604 // Create texture from resulting image
605 tex = driver->addTexture(name.c_str(), img);
606 guiScalingCache(io::path(name.c_str()), driver, img);
611 Add texture to caches (add NULL textures too)
614 MutexAutoLock lock(m_textureinfo_cache_mutex);
616 u32 id = m_textureinfo_cache.size();
617 TextureInfo ti(name, tex);
618 m_textureinfo_cache.push_back(ti);
619 m_name_to_id[name] = id;
624 std::string TextureSource::getTextureName(u32 id)
626 MutexAutoLock lock(m_textureinfo_cache_mutex);
628 if (id >= m_textureinfo_cache.size())
630 errorstream<<"TextureSource::getTextureName(): id="<<id
631 <<" >= m_textureinfo_cache.size()="
632 <<m_textureinfo_cache.size()<<std::endl;
636 return m_textureinfo_cache[id].name;
639 video::ITexture* TextureSource::getTexture(u32 id)
641 MutexAutoLock lock(m_textureinfo_cache_mutex);
643 if (id >= m_textureinfo_cache.size())
646 return m_textureinfo_cache[id].texture;
649 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
651 u32 actual_id = getTextureId(name);
655 return getTexture(actual_id);
658 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
660 return getTexture(name + "^[applyfiltersformesh", id);
663 Palette* TextureSource::getPalette(const std::string &name)
665 // Only the main thread may load images
666 sanity_check(std::this_thread::get_id() == m_main_thread);
671 auto it = m_palettes.find(name);
672 if (it == m_palettes.end()) {
674 video::IImage *img = generateImage(name);
676 warningstream << "TextureSource::getPalette(): palette \"" << name
677 << "\" could not be loaded." << std::endl;
681 u32 w = img->getDimension().Width;
682 u32 h = img->getDimension().Height;
683 // Real area of the image
688 warningstream << "TextureSource::getPalette(): the specified"
689 << " palette image \"" << name << "\" is larger than 256"
690 << " pixels, using the first 256." << std::endl;
692 } else if (256 % area != 0)
693 warningstream << "TextureSource::getPalette(): the "
694 << "specified palette image \"" << name << "\" does not "
695 << "contain power of two pixels." << std::endl;
696 // We stretch the palette so it will fit 256 values
697 // This many param2 values will have the same color
698 u32 step = 256 / area;
699 // For each pixel in the image
700 for (u32 i = 0; i < area; i++) {
701 video::SColor c = img->getPixel(i % w, i / w);
702 // Fill in palette with 'step' colors
703 for (u32 j = 0; j < step; j++)
704 new_palette.push_back(c);
707 // Fill in remaining elements
708 while (new_palette.size() < 256)
709 new_palette.emplace_back(0xFFFFFFFF);
710 m_palettes[name] = new_palette;
711 it = m_palettes.find(name);
713 if (it != m_palettes.end())
714 return &((*it).second);
718 void TextureSource::processQueue()
723 //NOTE this is only thread safe for ONE consumer thread!
724 if (!m_get_texture_queue.empty())
726 GetRequest<std::string, u32, u8, u8>
727 request = m_get_texture_queue.pop();
729 /*infostream<<"TextureSource::processQueue(): "
730 <<"got texture request with "
731 <<"name=\""<<request.key<<"\""
734 m_get_texture_queue.pushResult(request, generateTexture(request.key));
738 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
740 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
742 sanity_check(std::this_thread::get_id() == m_main_thread);
744 m_sourcecache.insert(name, img, true);
745 m_source_image_existence.set(name, true);
748 void TextureSource::rebuildImagesAndTextures()
750 MutexAutoLock lock(m_textureinfo_cache_mutex);
752 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
753 sanity_check(driver);
756 for (TextureInfo &ti : m_textureinfo_cache) {
757 video::IImage *img = generateImage(ti.name);
759 img = Align2Npot2(img, driver);
761 // Create texture from resulting image
762 video::ITexture *t = NULL;
764 t = driver->addTexture(ti.name.c_str(), img);
765 guiScalingCache(io::path(ti.name.c_str()), driver, img);
768 video::ITexture *t_old = ti.texture;
773 m_texture_trash.push_back(t_old);
777 video::ITexture* TextureSource::generateTextureFromMesh(
778 const TextureFromMeshParams ¶ms)
780 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
781 sanity_check(driver);
784 const GLubyte* renderstr = glGetString(GL_RENDERER);
785 std::string renderer((char*) renderstr);
787 // use no render to texture hack
789 (renderer.find("Adreno") != std::string::npos) ||
790 (renderer.find("Mali") != std::string::npos) ||
791 (renderer.find("Immersion") != std::string::npos) ||
792 (renderer.find("Tegra") != std::string::npos) ||
793 g_settings->getBool("inventory_image_hack")
795 // Get a scene manager
796 scene::ISceneManager *smgr_main = m_device->getSceneManager();
797 sanity_check(smgr_main);
798 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
801 const float scaling = 0.2;
803 scene::IMeshSceneNode* meshnode =
804 smgr->addMeshSceneNode(params.mesh, NULL,
805 -1, v3f(0,0,0), v3f(0,0,0),
806 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
807 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
808 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
809 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
810 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
811 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
813 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
814 params.camera_position, params.camera_lookat);
815 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
816 camera->setProjectionMatrix(params.camera_projection_matrix, false);
818 smgr->setAmbientLight(params.ambient_light);
819 smgr->addLightSceneNode(0,
820 params.light_position,
822 params.light_radius*scaling);
824 core::dimension2d<u32> screen = driver->getScreenSize();
827 driver->beginScene(true, true, video::SColor(0,0,0,0));
828 driver->clearZBuffer();
831 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
833 irr::video::IImage* rawImage =
834 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
836 u8* pixels = static_cast<u8*>(rawImage->lock());
843 core::rect<s32> source(
844 screen.Width /2 - (screen.Width * (scaling / 2)),
845 screen.Height/2 - (screen.Height * (scaling / 2)),
846 screen.Width /2 + (screen.Width * (scaling / 2)),
847 screen.Height/2 + (screen.Height * (scaling / 2))
850 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
851 partsize.Width, partsize.Height, GL_RGBA,
852 GL_UNSIGNED_BYTE, pixels);
856 // Drop scene manager
859 unsigned int pixelcount = partsize.Width*partsize.Height;
862 for (unsigned int i=0; i < pixelcount; i++) {
880 video::IImage* inventory_image =
881 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
883 rawImage->copyToScaling(inventory_image);
886 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
888 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
889 inventory_image->drop();
892 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
896 driver->makeColorKeyTexture(rtt, v2s32(0,0));
898 if (params.delete_texture_on_shutdown)
899 m_texture_trash.push_back(rtt);
905 if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) {
906 static bool warned = false;
909 errorstream<<"TextureSource::generateTextureFromMesh(): "
910 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
916 // Create render target texture
917 video::ITexture *rtt = driver->addRenderTargetTexture(
918 params.dim, params.rtt_texture_name.c_str(),
919 video::ECF_A8R8G8B8);
922 errorstream<<"TextureSource::generateTextureFromMesh(): "
923 <<"addRenderTargetTexture returned NULL."<<std::endl;
928 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
929 driver->removeTexture(rtt);
930 errorstream<<"TextureSource::generateTextureFromMesh(): "
931 <<"failed to set render target"<<std::endl;
935 // Get a scene manager
936 scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager();
938 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
941 scene::IMeshSceneNode* meshnode =
942 smgr->addMeshSceneNode(params.mesh, NULL,
943 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
944 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
945 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
946 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
947 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
948 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
950 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
951 params.camera_position, params.camera_lookat);
952 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
953 camera->setProjectionMatrix(params.camera_projection_matrix, false);
955 smgr->setAmbientLight(params.ambient_light);
956 smgr->addLightSceneNode(0,
957 params.light_position,
959 params.light_radius);
962 driver->beginScene(true, true, video::SColor(0,0,0,0));
966 // Drop scene manager
969 // Unset render target
970 driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
972 if (params.delete_texture_on_shutdown)
973 m_texture_trash.push_back(rtt);
978 video::IImage* TextureSource::generateImage(const std::string &name)
980 // Get the base image
982 const char separator = '^';
983 const char escape = '\\';
984 const char paren_open = '(';
985 const char paren_close = ')';
987 // Find last separator in the name
988 s32 last_separator_pos = -1;
990 for (s32 i = name.size() - 1; i >= 0; i--) {
991 if (i > 0 && name[i-1] == escape)
995 if (paren_bal == 0) {
996 last_separator_pos = i;
997 i = -1; // break out of loop
1001 if (paren_bal == 0) {
1002 errorstream << "generateImage(): unbalanced parentheses"
1003 << "(extranous '(') while generating texture \""
1004 << name << "\"" << std::endl;
1016 if (paren_bal > 0) {
1017 errorstream << "generateImage(): unbalanced parentheses"
1018 << "(missing matching '(') while generating texture \""
1019 << name << "\"" << std::endl;
1024 video::IImage *baseimg = NULL;
1027 If separator was found, make the base image
1028 using a recursive call.
1030 if (last_separator_pos != -1) {
1031 baseimg = generateImage(name.substr(0, last_separator_pos));
1035 Parse out the last part of the name of the image and act
1039 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1042 If this name is enclosed in parentheses, generate it
1043 and blit it onto the base image
1045 if (last_part_of_name[0] == paren_open
1046 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1047 std::string name2 = last_part_of_name.substr(1,
1048 last_part_of_name.size() - 2);
1049 video::IImage *tmp = generateImage(name2);
1051 errorstream << "generateImage(): "
1052 "Failed to generate \"" << name2 << "\""
1056 core::dimension2d<u32> dim = tmp->getDimension();
1058 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1063 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1064 // Generate image according to part of name
1065 errorstream << "generateImage(): "
1066 "Failed to generate \"" << last_part_of_name << "\""
1070 // If no resulting image, print a warning
1071 if (baseimg == NULL) {
1072 errorstream << "generateImage(): baseimg is NULL (attempted to"
1073 " create texture \"" << name << "\")" << std::endl;
1080 #include <GLES/gl.h>
1082 * Check and align image to npot2 if required by hardware
1083 * @param image image to check for npot2 alignment
1084 * @param driver driver to use for image operations
1085 * @return image or copy of image aligned to npot2
1088 inline u16 get_GL_major_version()
1090 const GLubyte *gl_version = glGetString(GL_VERSION);
1091 return (u16) (gl_version[0] - '0');
1094 video::IImage * Align2Npot2(video::IImage * image,
1095 video::IVideoDriver* driver)
1097 if (image == NULL) {
1101 core::dimension2d<u32> dim = image->getDimension();
1103 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1105 // Only GLES2 is trusted to correctly report npot support
1106 if (get_GL_major_version() > 1 &&
1107 extensions.find("GL_OES_texture_npot") != std::string::npos) {
1111 unsigned int height = npot2(dim.Height);
1112 unsigned int width = npot2(dim.Width);
1114 if ((dim.Height == height) &&
1115 (dim.Width == width)) {
1119 if (dim.Height > height) {
1123 if (dim.Width > width) {
1127 video::IImage *targetimage =
1128 driver->createImage(video::ECF_A8R8G8B8,
1129 core::dimension2d<u32>(width, height));
1131 if (targetimage != NULL) {
1132 image->copyToScaling(targetimage);
1140 static std::string unescape_string(const std::string &str, const char esc = '\\')
1143 size_t pos = 0, cpos;
1144 out.reserve(str.size());
1146 cpos = str.find_first_of(esc, pos);
1147 if (cpos == std::string::npos) {
1148 out += str.substr(pos);
1151 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1157 bool TextureSource::generateImagePart(std::string part_of_name,
1158 video::IImage *& baseimg)
1160 const char escape = '\\'; // same as in generateImage()
1161 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1162 sanity_check(driver);
1164 // Stuff starting with [ are special commands
1165 if (part_of_name.empty() || part_of_name[0] != '[') {
1166 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1168 image = Align2Npot2(image, driver);
1170 if (image == NULL) {
1171 if (!part_of_name.empty()) {
1173 // Do not create normalmap dummies
1174 if (part_of_name.find("_normal.png") != std::string::npos) {
1175 warningstream << "generateImage(): Could not load normal map \""
1176 << part_of_name << "\"" << std::endl;
1180 errorstream << "generateImage(): Could not load image \""
1181 << part_of_name << "\" while building texture; "
1182 "Creating a dummy image" << std::endl;
1185 // Just create a dummy image
1186 //core::dimension2d<u32> dim(2,2);
1187 core::dimension2d<u32> dim(1,1);
1188 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1189 sanity_check(image != NULL);
1190 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1191 image->setPixel(1,0, video::SColor(255,0,255,0));
1192 image->setPixel(0,1, video::SColor(255,0,0,255));
1193 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1194 image->setPixel(0,0, video::SColor(255,myrand()%256,
1195 myrand()%256,myrand()%256));
1196 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1197 myrand()%256,myrand()%256));
1198 image->setPixel(0,1, video::SColor(255,myrand()%256,
1199 myrand()%256,myrand()%256));
1200 image->setPixel(1,1, video::SColor(255,myrand()%256,
1201 myrand()%256,myrand()%256));*/
1204 // If base image is NULL, load as base.
1205 if (baseimg == NULL)
1207 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1209 Copy it this way to get an alpha channel.
1210 Otherwise images with alpha cannot be blitted on
1211 images that don't have alpha in the original file.
1213 core::dimension2d<u32> dim = image->getDimension();
1214 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1215 image->copyTo(baseimg);
1217 // Else blit on base.
1220 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1221 // Size of the copied area
1222 core::dimension2d<u32> dim = image->getDimension();
1223 //core::dimension2d<u32> dim(16,16);
1224 // Position to copy the blitted to in the base image
1225 core::position2d<s32> pos_to(0,0);
1226 // Position to copy the blitted from in the blitted image
1227 core::position2d<s32> pos_from(0,0);
1229 /*image->copyToWithAlpha(baseimg, pos_to,
1230 core::rect<s32>(pos_from, dim),
1231 video::SColor(255,255,255,255),
1234 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1235 if (dim == dim_dst) {
1236 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1237 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1238 // Upscale overlying image
1239 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1240 createImage(video::ECF_A8R8G8B8, dim_dst);
1241 image->copyToScaling(scaled_image);
1243 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1244 scaled_image->drop();
1246 // Upscale base image
1247 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1248 createImage(video::ECF_A8R8G8B8, dim);
1249 baseimg->copyToScaling(scaled_base);
1251 baseimg = scaled_base;
1253 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1261 // A special texture modification
1263 /*infostream<<"generateImage(): generating special "
1264 <<"modification \""<<part_of_name<<"\""
1270 Adds a cracking texture
1271 N = animation frame count, P = crack progression
1273 if (str_starts_with(part_of_name, "[crack"))
1275 if (baseimg == NULL) {
1276 errorstream<<"generateImagePart(): baseimg == NULL "
1277 <<"for part_of_name=\""<<part_of_name
1278 <<"\", cancelling."<<std::endl;
1282 // Crack image number and overlay option
1283 // Format: crack[o][:<tiles>]:<frame_count>:<frame>
1284 bool use_overlay = (part_of_name[6] == 'o');
1285 Strfnd sf(part_of_name);
1287 s32 frame_count = stoi(sf.next(":"));
1288 s32 progression = stoi(sf.next(":"));
1290 // Check whether there is the <tiles> argument, that is,
1291 // whether there are 3 arguments. If so, shift values
1292 // as the first and not the last argument is optional.
1293 auto s = sf.next(":");
1295 tiles = frame_count;
1296 frame_count = progression;
1297 progression = stoi(s);
1300 if (progression >= 0) {
1304 It is an image with a number of cracking stages
1307 video::IImage *img_crack = m_sourcecache.getOrLoad(
1308 "crack_anylength.png");
1311 draw_crack(img_crack, baseimg,
1312 use_overlay, frame_count,
1313 progression, driver, tiles);
1319 [combine:WxH:X,Y=filename:X,Y=filename2
1320 Creates a bigger texture from any amount of smaller ones
1322 else if (str_starts_with(part_of_name, "[combine"))
1324 Strfnd sf(part_of_name);
1326 u32 w0 = stoi(sf.next("x"));
1327 u32 h0 = stoi(sf.next(":"));
1328 core::dimension2d<u32> dim(w0,h0);
1329 if (baseimg == NULL) {
1330 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1331 baseimg->fill(video::SColor(0,0,0,0));
1333 while (!sf.at_end()) {
1334 u32 x = stoi(sf.next(","));
1335 u32 y = stoi(sf.next("="));
1336 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1337 infostream<<"Adding \""<<filename
1338 <<"\" to combined ("<<x<<","<<y<<")"
1340 video::IImage *img = generateImage(filename);
1342 core::dimension2d<u32> dim = img->getDimension();
1343 infostream<<"Size "<<dim.Width
1344 <<"x"<<dim.Height<<std::endl;
1345 core::position2d<s32> pos_base(x, y);
1346 video::IImage *img2 =
1347 driver->createImage(video::ECF_A8R8G8B8, dim);
1350 /*img2->copyToWithAlpha(baseimg, pos_base,
1351 core::rect<s32>(v2s32(0,0), dim),
1352 video::SColor(255,255,255,255),
1354 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1357 errorstream << "generateImagePart(): Failed to load image \""
1358 << filename << "\" for [combine" << std::endl;
1365 else if (str_starts_with(part_of_name, "[brighten"))
1367 if (baseimg == NULL) {
1368 errorstream<<"generateImagePart(): baseimg==NULL "
1369 <<"for part_of_name=\""<<part_of_name
1370 <<"\", cancelling."<<std::endl;
1378 Make image completely opaque.
1379 Used for the leaves texture when in old leaves mode, so
1380 that the transparent parts don't look completely black
1381 when simple alpha channel is used for rendering.
1383 else if (str_starts_with(part_of_name, "[noalpha"))
1385 if (baseimg == NULL){
1386 errorstream<<"generateImagePart(): baseimg==NULL "
1387 <<"for part_of_name=\""<<part_of_name
1388 <<"\", cancelling."<<std::endl;
1392 core::dimension2d<u32> dim = baseimg->getDimension();
1394 // Set alpha to full
1395 for (u32 y=0; y<dim.Height; y++)
1396 for (u32 x=0; x<dim.Width; x++)
1398 video::SColor c = baseimg->getPixel(x,y);
1400 baseimg->setPixel(x,y,c);
1405 Convert one color to transparent.
1407 else if (str_starts_with(part_of_name, "[makealpha:"))
1409 if (baseimg == NULL) {
1410 errorstream<<"generateImagePart(): baseimg == NULL "
1411 <<"for part_of_name=\""<<part_of_name
1412 <<"\", cancelling."<<std::endl;
1416 Strfnd sf(part_of_name.substr(11));
1417 u32 r1 = stoi(sf.next(","));
1418 u32 g1 = stoi(sf.next(","));
1419 u32 b1 = stoi(sf.next(""));
1421 core::dimension2d<u32> dim = baseimg->getDimension();
1423 /*video::IImage *oldbaseimg = baseimg;
1424 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1425 oldbaseimg->copyTo(baseimg);
1426 oldbaseimg->drop();*/
1428 // Set alpha to full
1429 for (u32 y=0; y<dim.Height; y++)
1430 for (u32 x=0; x<dim.Width; x++)
1432 video::SColor c = baseimg->getPixel(x,y);
1434 u32 g = c.getGreen();
1435 u32 b = c.getBlue();
1436 if (!(r == r1 && g == g1 && b == b1))
1439 baseimg->setPixel(x,y,c);
1444 Rotates and/or flips the image.
1446 N can be a number (between 0 and 7) or a transform name.
1447 Rotations are counter-clockwise.
1449 1 R90 rotate by 90 degrees
1450 2 R180 rotate by 180 degrees
1451 3 R270 rotate by 270 degrees
1453 5 FXR90 flip X then rotate by 90 degrees
1455 7 FYR90 flip Y then rotate by 90 degrees
1457 Note: Transform names can be concatenated to produce
1458 their product (applies the first then the second).
1459 The resulting transform will be equivalent to one of the
1460 eight existing ones, though (see: dihedral group).
1462 else if (str_starts_with(part_of_name, "[transform"))
1464 if (baseimg == NULL) {
1465 errorstream<<"generateImagePart(): baseimg == NULL "
1466 <<"for part_of_name=\""<<part_of_name
1467 <<"\", cancelling."<<std::endl;
1471 u32 transform = parseImageTransform(part_of_name.substr(10));
1472 core::dimension2d<u32> dim = imageTransformDimension(
1473 transform, baseimg->getDimension());
1474 video::IImage *image = driver->createImage(
1475 baseimg->getColorFormat(), dim);
1476 sanity_check(image != NULL);
1477 imageTransform(transform, baseimg, image);
1482 [inventorycube{topimage{leftimage{rightimage
1483 In every subimage, replace ^ with &.
1484 Create an "inventory cube".
1485 NOTE: This should be used only on its own.
1486 Example (a grass block (not actually used in game):
1487 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1489 else if (str_starts_with(part_of_name, "[inventorycube"))
1491 if (baseimg != NULL){
1492 errorstream<<"generateImagePart(): baseimg != NULL "
1493 <<"for part_of_name=\""<<part_of_name
1494 <<"\", cancelling."<<std::endl;
1498 str_replace(part_of_name, '&', '^');
1499 Strfnd sf(part_of_name);
1501 std::string imagename_top = sf.next("{");
1502 std::string imagename_left = sf.next("{");
1503 std::string imagename_right = sf.next("{");
1505 // Generate images for the faces of the cube
1506 video::IImage *img_top = generateImage(imagename_top);
1507 video::IImage *img_left = generateImage(imagename_left);
1508 video::IImage *img_right = generateImage(imagename_right);
1510 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1511 errorstream << "generateImagePart(): Failed to create textures"
1512 << " for inventorycube \"" << part_of_name << "\""
1514 baseimg = generateImage(imagename_top);
1519 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1520 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1522 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1523 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1525 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1526 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1529 // Create textures from images
1530 video::ITexture *texture_top = driver->addTexture(
1531 (imagename_top + "__temp__").c_str(), img_top);
1532 video::ITexture *texture_left = driver->addTexture(
1533 (imagename_left + "__temp__").c_str(), img_left);
1534 video::ITexture *texture_right = driver->addTexture(
1535 (imagename_right + "__temp__").c_str(), img_right);
1536 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1544 Draw a cube mesh into a render target texture
1546 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1547 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1548 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1549 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1550 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1551 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1552 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1553 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1555 TextureFromMeshParams params;
1557 params.dim.set(64, 64);
1558 params.rtt_texture_name = part_of_name + "_RTT";
1559 // We will delete the rtt texture ourselves
1560 params.delete_texture_on_shutdown = false;
1561 params.camera_position.set(0, 1.0, -1.5);
1562 params.camera_position.rotateXZBy(45);
1563 params.camera_lookat.set(0, 0, 0);
1564 // Set orthogonal projection
1565 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1566 1.65, 1.65, 0, 100);
1568 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1569 params.light_position.set(10, 100, -50);
1570 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1571 params.light_radius = 1000;
1573 video::ITexture *rtt = generateTextureFromMesh(params);
1579 driver->removeTexture(texture_top);
1580 driver->removeTexture(texture_left);
1581 driver->removeTexture(texture_right);
1584 baseimg = generateImage(imagename_top);
1588 // Create image of render target
1589 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1590 FATAL_ERROR_IF(!image, "Could not create image of render target");
1593 driver->removeTexture(rtt);
1595 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1598 image->copyTo(baseimg);
1603 [lowpart:percent:filename
1604 Adds the lower part of a texture
1606 else if (str_starts_with(part_of_name, "[lowpart:"))
1608 Strfnd sf(part_of_name);
1610 u32 percent = stoi(sf.next(":"));
1611 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1613 if (baseimg == NULL)
1614 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1615 video::IImage *img = generateImage(filename);
1618 core::dimension2d<u32> dim = img->getDimension();
1619 core::position2d<s32> pos_base(0, 0);
1620 video::IImage *img2 =
1621 driver->createImage(video::ECF_A8R8G8B8, dim);
1624 core::position2d<s32> clippos(0, 0);
1625 clippos.Y = dim.Height * (100-percent) / 100;
1626 core::dimension2d<u32> clipdim = dim;
1627 clipdim.Height = clipdim.Height * percent / 100 + 1;
1628 core::rect<s32> cliprect(clippos, clipdim);
1629 img2->copyToWithAlpha(baseimg, pos_base,
1630 core::rect<s32>(v2s32(0,0), dim),
1631 video::SColor(255,255,255,255),
1638 Crops a frame of a vertical animation.
1639 N = frame count, I = frame index
1641 else if (str_starts_with(part_of_name, "[verticalframe:"))
1643 Strfnd sf(part_of_name);
1645 u32 frame_count = stoi(sf.next(":"));
1646 u32 frame_index = stoi(sf.next(":"));
1648 if (baseimg == NULL){
1649 errorstream<<"generateImagePart(): baseimg != NULL "
1650 <<"for part_of_name=\""<<part_of_name
1651 <<"\", cancelling."<<std::endl;
1655 v2u32 frame_size = baseimg->getDimension();
1656 frame_size.Y /= frame_count;
1658 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1661 errorstream<<"generateImagePart(): Could not create image "
1662 <<"for part_of_name=\""<<part_of_name
1663 <<"\", cancelling."<<std::endl;
1667 // Fill target image with transparency
1668 img->fill(video::SColor(0,0,0,0));
1670 core::dimension2d<u32> dim = frame_size;
1671 core::position2d<s32> pos_dst(0, 0);
1672 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1673 baseimg->copyToWithAlpha(img, pos_dst,
1674 core::rect<s32>(pos_src, dim),
1675 video::SColor(255,255,255,255),
1683 Applies a mask to an image
1685 else if (str_starts_with(part_of_name, "[mask:"))
1687 if (baseimg == NULL) {
1688 errorstream << "generateImage(): baseimg == NULL "
1689 << "for part_of_name=\"" << part_of_name
1690 << "\", cancelling." << std::endl;
1693 Strfnd sf(part_of_name);
1695 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1697 video::IImage *img = generateImage(filename);
1699 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1700 img->getDimension());
1703 errorstream << "generateImage(): Failed to load \""
1704 << filename << "\".";
1709 multiplys a given color to any pixel of an image
1710 color = color as ColorString
1712 else if (str_starts_with(part_of_name, "[multiply:")) {
1713 Strfnd sf(part_of_name);
1715 std::string color_str = sf.next(":");
1717 if (baseimg == NULL) {
1718 errorstream << "generateImagePart(): baseimg != NULL "
1719 << "for part_of_name=\"" << part_of_name
1720 << "\", cancelling." << std::endl;
1724 video::SColor color;
1726 if (!parseColorString(color_str, color, false))
1729 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1733 Overlays image with given color
1734 color = color as ColorString
1736 else if (str_starts_with(part_of_name, "[colorize:"))
1738 Strfnd sf(part_of_name);
1740 std::string color_str = sf.next(":");
1741 std::string ratio_str = sf.next(":");
1743 if (baseimg == NULL) {
1744 errorstream << "generateImagePart(): baseimg != NULL "
1745 << "for part_of_name=\"" << part_of_name
1746 << "\", cancelling." << std::endl;
1750 video::SColor color;
1752 bool keep_alpha = false;
1754 if (!parseColorString(color_str, color, false))
1757 if (is_number(ratio_str))
1758 ratio = mystoi(ratio_str, 0, 255);
1759 else if (ratio_str == "alpha")
1762 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1765 [applyfiltersformesh
1768 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1770 // Apply the "clean transparent" filter, if configured.
1771 if (g_settings->getBool("texture_clean_transparent"))
1772 imageCleanTransparent(baseimg, 127);
1774 /* Upscale textures to user's requested minimum size. This is a trick to make
1775 * filters look as good on low-res textures as on high-res ones, by making
1776 * low-res textures BECOME high-res ones. This is helpful for worlds that
1777 * mix high- and low-res textures, or for mods with least-common-denominator
1778 * textures that don't have the resources to offer high-res alternatives.
1780 s32 scaleto = g_settings->getS32("texture_min_size");
1782 const core::dimension2d<u32> dim = baseimg->getDimension();
1784 /* Calculate scaling needed to make the shortest texture dimension
1785 * equal to the target minimum. If e.g. this is a vertical frames
1786 * animation, the short dimension will be the real size.
1788 if ((dim.Width == 0) || (dim.Height == 0)) {
1789 errorstream << "generateImagePart(): Illegal 0 dimension "
1790 << "for part_of_name=\""<< part_of_name
1791 << "\", cancelling." << std::endl;
1794 u32 xscale = scaleto / dim.Width;
1795 u32 yscale = scaleto / dim.Height;
1796 u32 scale = (xscale > yscale) ? xscale : yscale;
1798 // Never downscale; only scale up by 2x or more.
1800 u32 w = scale * dim.Width;
1801 u32 h = scale * dim.Height;
1802 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1803 video::IImage *newimg = driver->createImage(
1804 baseimg->getColorFormat(), newdim);
1805 baseimg->copyToScaling(newimg);
1813 Resizes the base image to the given dimensions
1815 else if (str_starts_with(part_of_name, "[resize"))
1817 if (baseimg == NULL) {
1818 errorstream << "generateImagePart(): baseimg == NULL "
1819 << "for part_of_name=\""<< part_of_name
1820 << "\", cancelling." << std::endl;
1824 Strfnd sf(part_of_name);
1826 u32 width = stoi(sf.next("x"));
1827 u32 height = stoi(sf.next(""));
1828 core::dimension2d<u32> dim(width, height);
1830 video::IImage *image = RenderingEngine::get_video_driver()->
1831 createImage(video::ECF_A8R8G8B8, dim);
1832 baseimg->copyToScaling(image);
1838 Makes the base image transparent according to the given ratio.
1839 R must be between 0 and 255.
1840 0 means totally transparent.
1841 255 means totally opaque.
1843 else if (str_starts_with(part_of_name, "[opacity:")) {
1844 if (baseimg == NULL) {
1845 errorstream << "generateImagePart(): baseimg == NULL "
1846 << "for part_of_name=\"" << part_of_name
1847 << "\", cancelling." << std::endl;
1851 Strfnd sf(part_of_name);
1854 u32 ratio = mystoi(sf.next(""), 0, 255);
1856 core::dimension2d<u32> dim = baseimg->getDimension();
1858 for (u32 y = 0; y < dim.Height; y++)
1859 for (u32 x = 0; x < dim.Width; x++)
1861 video::SColor c = baseimg->getPixel(x, y);
1862 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1863 baseimg->setPixel(x, y, c);
1868 Inverts the given channels of the base image.
1869 Mode may contain the characters "r", "g", "b", "a".
1870 Only the channels that are mentioned in the mode string
1873 else if (str_starts_with(part_of_name, "[invert:")) {
1874 if (baseimg == NULL) {
1875 errorstream << "generateImagePart(): baseimg == NULL "
1876 << "for part_of_name=\"" << part_of_name
1877 << "\", cancelling." << std::endl;
1881 Strfnd sf(part_of_name);
1884 std::string mode = sf.next("");
1886 if (mode.find('a') != std::string::npos)
1887 mask |= 0xff000000UL;
1888 if (mode.find('r') != std::string::npos)
1889 mask |= 0x00ff0000UL;
1890 if (mode.find('g') != std::string::npos)
1891 mask |= 0x0000ff00UL;
1892 if (mode.find('b') != std::string::npos)
1893 mask |= 0x000000ffUL;
1895 core::dimension2d<u32> dim = baseimg->getDimension();
1897 for (u32 y = 0; y < dim.Height; y++)
1898 for (u32 x = 0; x < dim.Width; x++)
1900 video::SColor c = baseimg->getPixel(x, y);
1902 baseimg->setPixel(x, y, c);
1907 Retrieves a tile at position X,Y (in tiles)
1908 from the base image it assumes to be a
1909 tilesheet with dimensions W,H (in tiles).
1911 else if (part_of_name.substr(0,7) == "[sheet:") {
1912 if (baseimg == NULL) {
1913 errorstream << "generateImagePart(): baseimg != NULL "
1914 << "for part_of_name=\"" << part_of_name
1915 << "\", cancelling." << std::endl;
1919 Strfnd sf(part_of_name);
1921 u32 w0 = stoi(sf.next("x"));
1922 u32 h0 = stoi(sf.next(":"));
1923 u32 x0 = stoi(sf.next(","));
1924 u32 y0 = stoi(sf.next(":"));
1926 core::dimension2d<u32> img_dim = baseimg->getDimension();
1927 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1929 video::IImage *img = driver->createImage(
1930 video::ECF_A8R8G8B8, tile_dim);
1932 errorstream << "generateImagePart(): Could not create image "
1933 << "for part_of_name=\"" << part_of_name
1934 << "\", cancelling." << std::endl;
1938 img->fill(video::SColor(0,0,0,0));
1939 v2u32 vdim(tile_dim);
1940 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1941 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1942 video::SColor(255,255,255,255), NULL);
1950 errorstream << "generateImagePart(): Invalid "
1951 " modification: \"" << part_of_name << "\"" << std::endl;
1959 Draw an image on top of an another one, using the alpha channel of the
1962 This exists because IImage::copyToWithAlpha() doesn't seem to always
1965 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1966 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1968 for (u32 y0=0; y0<size.Y; y0++)
1969 for (u32 x0=0; x0<size.X; x0++)
1971 s32 src_x = src_pos.X + x0;
1972 s32 src_y = src_pos.Y + y0;
1973 s32 dst_x = dst_pos.X + x0;
1974 s32 dst_y = dst_pos.Y + y0;
1975 video::SColor src_c = src->getPixel(src_x, src_y);
1976 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1977 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1978 dst->setPixel(dst_x, dst_y, dst_c);
1983 Draw an image on top of an another one, using the alpha channel of the
1984 source image; only modify fully opaque pixels in destinaion
1986 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1987 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1989 for (u32 y0=0; y0<size.Y; y0++)
1990 for (u32 x0=0; x0<size.X; x0++)
1992 s32 src_x = src_pos.X + x0;
1993 s32 src_y = src_pos.Y + y0;
1994 s32 dst_x = dst_pos.X + x0;
1995 s32 dst_y = dst_pos.Y + y0;
1996 video::SColor src_c = src->getPixel(src_x, src_y);
1997 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1998 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
2000 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2001 dst->setPixel(dst_x, dst_y, dst_c);
2006 // This function has been disabled because it is currently unused.
2007 // Feel free to re-enable if you find it handy.
2010 Draw an image on top of an another one, using the specified ratio
2011 modify all partially-opaque pixels in the destination.
2013 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2014 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2016 for (u32 y0 = 0; y0 < size.Y; y0++)
2017 for (u32 x0 = 0; x0 < size.X; x0++)
2019 s32 src_x = src_pos.X + x0;
2020 s32 src_y = src_pos.Y + y0;
2021 s32 dst_x = dst_pos.X + x0;
2022 s32 dst_y = dst_pos.Y + y0;
2023 video::SColor src_c = src->getPixel(src_x, src_y);
2024 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2025 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2028 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2030 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2031 dst->setPixel(dst_x, dst_y, dst_c);
2038 Apply color to destination
2040 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2041 const video::SColor &color, int ratio, bool keep_alpha)
2043 u32 alpha = color.getAlpha();
2044 video::SColor dst_c;
2045 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2046 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2048 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2049 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2050 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2051 if (dst_alpha > 0) {
2052 dst_c.setAlpha(dst_alpha * alpha / 255);
2053 dst->setPixel(x, y, dst_c);
2056 } else { // replace the color including the alpha
2057 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2058 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2059 if (dst->getPixel(x, y).getAlpha() > 0)
2060 dst->setPixel(x, y, color);
2062 } else { // interpolate between the color and destination
2063 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2064 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2065 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2066 dst_c = dst->getPixel(x, y);
2067 if (dst_c.getAlpha() > 0) {
2068 dst_c = color.getInterpolated(dst_c, interp);
2069 dst->setPixel(x, y, dst_c);
2076 Apply color to destination
2078 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2079 const video::SColor &color)
2081 video::SColor dst_c;
2083 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2084 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2085 dst_c = dst->getPixel(x, y);
2088 (dst_c.getRed() * color.getRed()) / 255,
2089 (dst_c.getGreen() * color.getGreen()) / 255,
2090 (dst_c.getBlue() * color.getBlue()) / 255
2092 dst->setPixel(x, y, dst_c);
2097 Apply mask to destination
2099 static void apply_mask(video::IImage *mask, video::IImage *dst,
2100 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2102 for (u32 y0 = 0; y0 < size.Y; y0++) {
2103 for (u32 x0 = 0; x0 < size.X; x0++) {
2104 s32 mask_x = x0 + mask_pos.X;
2105 s32 mask_y = y0 + mask_pos.Y;
2106 s32 dst_x = x0 + dst_pos.X;
2107 s32 dst_y = y0 + dst_pos.Y;
2108 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2109 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2110 dst_c.color &= mask_c.color;
2111 dst->setPixel(dst_x, dst_y, dst_c);
2116 video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
2117 core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
2119 core::dimension2d<u32> strip_size = crack->getDimension();
2120 core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
2121 core::dimension2d<u32> tile_size(size / tiles);
2122 s32 frame_count = strip_size.Height / strip_size.Width;
2123 if (frame_index >= frame_count)
2124 frame_index = frame_count - 1;
2125 core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
2126 video::IImage *result = nullptr;
2128 // extract crack frame
2129 video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
2132 if (tile_size == frame_size) {
2133 crack->copyTo(crack_tile, v2s32(0, 0), frame);
2135 video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
2137 goto exit__has_tile;
2138 crack->copyTo(crack_frame, v2s32(0, 0), frame);
2139 crack_frame->copyToScaling(crack_tile);
2140 crack_frame->drop();
2146 result = driver->createImage(video::ECF_A8R8G8B8, size);
2148 goto exit__has_tile;
2150 for (u8 i = 0; i < tiles; i++)
2151 for (u8 j = 0; j < tiles; j++)
2152 crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
2159 static void draw_crack(video::IImage *crack, video::IImage *dst,
2160 bool use_overlay, s32 frame_count, s32 progression,
2161 video::IVideoDriver *driver, u8 tiles)
2163 // Dimension of destination image
2164 core::dimension2d<u32> dim_dst = dst->getDimension();
2165 // Limit frame_count
2166 if (frame_count > (s32) dim_dst.Height)
2167 frame_count = dim_dst.Height;
2168 if (frame_count < 1)
2170 // Dimension of the scaled crack stage,
2171 // which is the same as the dimension of a single destination frame
2172 core::dimension2d<u32> frame_size(
2174 dim_dst.Height / frame_count
2176 video::IImage *crack_scaled = create_crack_image(crack, progression,
2177 frame_size, tiles, driver);
2181 auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
2182 for (s32 i = 0; i < frame_count; ++i) {
2183 v2s32 dst_pos(0, frame_size.Height * i);
2184 blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
2187 crack_scaled->drop();
2190 void brighten(video::IImage *image)
2195 core::dimension2d<u32> dim = image->getDimension();
2197 for (u32 y=0; y<dim.Height; y++)
2198 for (u32 x=0; x<dim.Width; x++)
2200 video::SColor c = image->getPixel(x,y);
2201 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2202 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2203 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2204 image->setPixel(x,y,c);
2208 u32 parseImageTransform(const std::string& s)
2210 int total_transform = 0;
2212 std::string transform_names[8];
2213 transform_names[0] = "i";
2214 transform_names[1] = "r90";
2215 transform_names[2] = "r180";
2216 transform_names[3] = "r270";
2217 transform_names[4] = "fx";
2218 transform_names[6] = "fy";
2220 std::size_t pos = 0;
2221 while(pos < s.size())
2224 for (int i = 0; i <= 7; ++i)
2226 const std::string &name_i = transform_names[i];
2228 if (s[pos] == ('0' + i))
2235 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2237 pos += name_i.size();
2244 // Multiply total_transform and transform in the group D4
2247 new_total = (transform + total_transform) % 4;
2249 new_total = (transform - total_transform + 8) % 4;
2250 if ((transform >= 4) ^ (total_transform >= 4))
2253 total_transform = new_total;
2255 return total_transform;
2258 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2260 if (transform % 2 == 0)
2263 return core::dimension2d<u32>(dim.Height, dim.Width);
2266 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2268 if (src == NULL || dst == NULL)
2271 core::dimension2d<u32> dstdim = dst->getDimension();
2274 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2275 assert(transform <= 7);
2278 Compute the transformation from source coordinates (sx,sy)
2279 to destination coordinates (dx,dy).
2283 if (transform == 0) // identity
2284 sxn = 0, syn = 2; // sx = dx, sy = dy
2285 else if (transform == 1) // rotate by 90 degrees ccw
2286 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2287 else if (transform == 2) // rotate by 180 degrees
2288 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2289 else if (transform == 3) // rotate by 270 degrees ccw
2290 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2291 else if (transform == 4) // flip x
2292 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2293 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2294 sxn = 2, syn = 0; // sx = dy, sy = dx
2295 else if (transform == 6) // flip y
2296 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2297 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2298 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2300 for (u32 dy=0; dy<dstdim.Height; dy++)
2301 for (u32 dx=0; dx<dstdim.Width; dx++)
2303 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2304 u32 sx = entries[sxn];
2305 u32 sy = entries[syn];
2306 video::SColor c = src->getPixel(sx,sy);
2307 dst->setPixel(dx,dy,c);
2311 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2313 if (isKnownSourceImage("override_normal.png"))
2314 return getTexture("override_normal.png");
2315 std::string fname_base = name;
2316 static const char *normal_ext = "_normal.png";
2317 static const u32 normal_ext_size = strlen(normal_ext);
2318 size_t pos = fname_base.find('.');
2319 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2320 if (isKnownSourceImage(fname_normal)) {
2321 // look for image extension and replace it
2323 while ((i = fname_base.find('.', i)) != std::string::npos) {
2324 fname_base.replace(i, 4, normal_ext);
2325 i += normal_ext_size;
2327 return getTexture(fname_base);
2332 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2334 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2335 video::SColor c(0, 0, 0, 0);
2336 video::ITexture *texture = getTexture(name);
2337 video::IImage *image = driver->createImage(texture,
2338 core::position2d<s32>(0, 0),
2339 texture->getOriginalSize());
2344 core::dimension2d<u32> dim = image->getDimension();
2347 step = dim.Width / 16;
2348 for (u16 x = 0; x < dim.Width; x += step) {
2349 for (u16 y = 0; y < dim.Width; y += step) {
2350 c = image->getPixel(x,y);
2351 if (c.getAlpha() > 0) {
2361 c.setRed(tR / total);
2362 c.setGreen(tG / total);
2363 c.setBlue(tB / total);
2370 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2372 std::string tname = "__shaderFlagsTexture";
2373 tname += normalmap_present ? "1" : "0";
2375 if (isKnownSourceImage(tname)) {
2376 return getTexture(tname);
2379 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2380 video::IImage *flags_image = driver->createImage(
2381 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2382 sanity_check(flags_image != NULL);
2383 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2384 flags_image->setPixel(0, 0, c);
2385 insertSourceImage(tname, flags_image);
2386 flags_image->drop();
2387 return getTexture(tname);