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);
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 bool use_overlay = (part_of_name[6] == 'o');
1284 Strfnd sf(part_of_name);
1286 s32 frame_count = stoi(sf.next(":"));
1287 s32 progression = stoi(sf.next(":"));
1289 if (progression >= 0) {
1293 It is an image with a number of cracking stages
1296 video::IImage *img_crack = m_sourcecache.getOrLoad(
1297 "crack_anylength.png");
1300 draw_crack(img_crack, baseimg,
1301 use_overlay, frame_count,
1302 progression, driver);
1308 [combine:WxH:X,Y=filename:X,Y=filename2
1309 Creates a bigger texture from any amount of smaller ones
1311 else if (str_starts_with(part_of_name, "[combine"))
1313 Strfnd sf(part_of_name);
1315 u32 w0 = stoi(sf.next("x"));
1316 u32 h0 = stoi(sf.next(":"));
1317 core::dimension2d<u32> dim(w0,h0);
1318 if (baseimg == NULL) {
1319 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1320 baseimg->fill(video::SColor(0,0,0,0));
1322 while (!sf.at_end()) {
1323 u32 x = stoi(sf.next(","));
1324 u32 y = stoi(sf.next("="));
1325 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1326 infostream<<"Adding \""<<filename
1327 <<"\" to combined ("<<x<<","<<y<<")"
1329 video::IImage *img = generateImage(filename);
1331 core::dimension2d<u32> dim = img->getDimension();
1332 infostream<<"Size "<<dim.Width
1333 <<"x"<<dim.Height<<std::endl;
1334 core::position2d<s32> pos_base(x, y);
1335 video::IImage *img2 =
1336 driver->createImage(video::ECF_A8R8G8B8, dim);
1339 /*img2->copyToWithAlpha(baseimg, pos_base,
1340 core::rect<s32>(v2s32(0,0), dim),
1341 video::SColor(255,255,255,255),
1343 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1346 errorstream << "generateImagePart(): Failed to load image \""
1347 << filename << "\" for [combine" << std::endl;
1354 else if (str_starts_with(part_of_name, "[brighten"))
1356 if (baseimg == NULL) {
1357 errorstream<<"generateImagePart(): baseimg==NULL "
1358 <<"for part_of_name=\""<<part_of_name
1359 <<"\", cancelling."<<std::endl;
1367 Make image completely opaque.
1368 Used for the leaves texture when in old leaves mode, so
1369 that the transparent parts don't look completely black
1370 when simple alpha channel is used for rendering.
1372 else if (str_starts_with(part_of_name, "[noalpha"))
1374 if (baseimg == NULL){
1375 errorstream<<"generateImagePart(): baseimg==NULL "
1376 <<"for part_of_name=\""<<part_of_name
1377 <<"\", cancelling."<<std::endl;
1381 core::dimension2d<u32> dim = baseimg->getDimension();
1383 // Set alpha to full
1384 for (u32 y=0; y<dim.Height; y++)
1385 for (u32 x=0; x<dim.Width; x++)
1387 video::SColor c = baseimg->getPixel(x,y);
1389 baseimg->setPixel(x,y,c);
1394 Convert one color to transparent.
1396 else if (str_starts_with(part_of_name, "[makealpha:"))
1398 if (baseimg == NULL) {
1399 errorstream<<"generateImagePart(): baseimg == NULL "
1400 <<"for part_of_name=\""<<part_of_name
1401 <<"\", cancelling."<<std::endl;
1405 Strfnd sf(part_of_name.substr(11));
1406 u32 r1 = stoi(sf.next(","));
1407 u32 g1 = stoi(sf.next(","));
1408 u32 b1 = stoi(sf.next(""));
1410 core::dimension2d<u32> dim = baseimg->getDimension();
1412 /*video::IImage *oldbaseimg = baseimg;
1413 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1414 oldbaseimg->copyTo(baseimg);
1415 oldbaseimg->drop();*/
1417 // Set alpha to full
1418 for (u32 y=0; y<dim.Height; y++)
1419 for (u32 x=0; x<dim.Width; x++)
1421 video::SColor c = baseimg->getPixel(x,y);
1423 u32 g = c.getGreen();
1424 u32 b = c.getBlue();
1425 if (!(r == r1 && g == g1 && b == b1))
1428 baseimg->setPixel(x,y,c);
1433 Rotates and/or flips the image.
1435 N can be a number (between 0 and 7) or a transform name.
1436 Rotations are counter-clockwise.
1438 1 R90 rotate by 90 degrees
1439 2 R180 rotate by 180 degrees
1440 3 R270 rotate by 270 degrees
1442 5 FXR90 flip X then rotate by 90 degrees
1444 7 FYR90 flip Y then rotate by 90 degrees
1446 Note: Transform names can be concatenated to produce
1447 their product (applies the first then the second).
1448 The resulting transform will be equivalent to one of the
1449 eight existing ones, though (see: dihedral group).
1451 else if (str_starts_with(part_of_name, "[transform"))
1453 if (baseimg == NULL) {
1454 errorstream<<"generateImagePart(): baseimg == NULL "
1455 <<"for part_of_name=\""<<part_of_name
1456 <<"\", cancelling."<<std::endl;
1460 u32 transform = parseImageTransform(part_of_name.substr(10));
1461 core::dimension2d<u32> dim = imageTransformDimension(
1462 transform, baseimg->getDimension());
1463 video::IImage *image = driver->createImage(
1464 baseimg->getColorFormat(), dim);
1465 sanity_check(image != NULL);
1466 imageTransform(transform, baseimg, image);
1471 [inventorycube{topimage{leftimage{rightimage
1472 In every subimage, replace ^ with &.
1473 Create an "inventory cube".
1474 NOTE: This should be used only on its own.
1475 Example (a grass block (not actually used in game):
1476 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1478 else if (str_starts_with(part_of_name, "[inventorycube"))
1480 if (baseimg != NULL){
1481 errorstream<<"generateImagePart(): baseimg != NULL "
1482 <<"for part_of_name=\""<<part_of_name
1483 <<"\", cancelling."<<std::endl;
1487 str_replace(part_of_name, '&', '^');
1488 Strfnd sf(part_of_name);
1490 std::string imagename_top = sf.next("{");
1491 std::string imagename_left = sf.next("{");
1492 std::string imagename_right = sf.next("{");
1494 // Generate images for the faces of the cube
1495 video::IImage *img_top = generateImage(imagename_top);
1496 video::IImage *img_left = generateImage(imagename_left);
1497 video::IImage *img_right = generateImage(imagename_right);
1499 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1500 errorstream << "generateImagePart(): Failed to create textures"
1501 << " for inventorycube \"" << part_of_name << "\""
1503 baseimg = generateImage(imagename_top);
1508 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1509 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1511 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1512 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1514 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1515 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1518 // Create textures from images
1519 video::ITexture *texture_top = driver->addTexture(
1520 (imagename_top + "__temp__").c_str(), img_top);
1521 video::ITexture *texture_left = driver->addTexture(
1522 (imagename_left + "__temp__").c_str(), img_left);
1523 video::ITexture *texture_right = driver->addTexture(
1524 (imagename_right + "__temp__").c_str(), img_right);
1525 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1533 Draw a cube mesh into a render target texture
1535 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1536 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1537 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1538 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1539 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1540 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1541 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1542 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1544 TextureFromMeshParams params;
1546 params.dim.set(64, 64);
1547 params.rtt_texture_name = part_of_name + "_RTT";
1548 // We will delete the rtt texture ourselves
1549 params.delete_texture_on_shutdown = false;
1550 params.camera_position.set(0, 1.0, -1.5);
1551 params.camera_position.rotateXZBy(45);
1552 params.camera_lookat.set(0, 0, 0);
1553 // Set orthogonal projection
1554 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1555 1.65, 1.65, 0, 100);
1557 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1558 params.light_position.set(10, 100, -50);
1559 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1560 params.light_radius = 1000;
1562 video::ITexture *rtt = generateTextureFromMesh(params);
1568 driver->removeTexture(texture_top);
1569 driver->removeTexture(texture_left);
1570 driver->removeTexture(texture_right);
1573 baseimg = generateImage(imagename_top);
1577 // Create image of render target
1578 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1579 FATAL_ERROR_IF(!image, "Could not create image of render target");
1582 driver->removeTexture(rtt);
1584 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1587 image->copyTo(baseimg);
1592 [lowpart:percent:filename
1593 Adds the lower part of a texture
1595 else if (str_starts_with(part_of_name, "[lowpart:"))
1597 Strfnd sf(part_of_name);
1599 u32 percent = stoi(sf.next(":"));
1600 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1602 if (baseimg == NULL)
1603 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1604 video::IImage *img = generateImage(filename);
1607 core::dimension2d<u32> dim = img->getDimension();
1608 core::position2d<s32> pos_base(0, 0);
1609 video::IImage *img2 =
1610 driver->createImage(video::ECF_A8R8G8B8, dim);
1613 core::position2d<s32> clippos(0, 0);
1614 clippos.Y = dim.Height * (100-percent) / 100;
1615 core::dimension2d<u32> clipdim = dim;
1616 clipdim.Height = clipdim.Height * percent / 100 + 1;
1617 core::rect<s32> cliprect(clippos, clipdim);
1618 img2->copyToWithAlpha(baseimg, pos_base,
1619 core::rect<s32>(v2s32(0,0), dim),
1620 video::SColor(255,255,255,255),
1627 Crops a frame of a vertical animation.
1628 N = frame count, I = frame index
1630 else if (str_starts_with(part_of_name, "[verticalframe:"))
1632 Strfnd sf(part_of_name);
1634 u32 frame_count = stoi(sf.next(":"));
1635 u32 frame_index = stoi(sf.next(":"));
1637 if (baseimg == NULL){
1638 errorstream<<"generateImagePart(): baseimg != NULL "
1639 <<"for part_of_name=\""<<part_of_name
1640 <<"\", cancelling."<<std::endl;
1644 v2u32 frame_size = baseimg->getDimension();
1645 frame_size.Y /= frame_count;
1647 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1650 errorstream<<"generateImagePart(): Could not create image "
1651 <<"for part_of_name=\""<<part_of_name
1652 <<"\", cancelling."<<std::endl;
1656 // Fill target image with transparency
1657 img->fill(video::SColor(0,0,0,0));
1659 core::dimension2d<u32> dim = frame_size;
1660 core::position2d<s32> pos_dst(0, 0);
1661 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1662 baseimg->copyToWithAlpha(img, pos_dst,
1663 core::rect<s32>(pos_src, dim),
1664 video::SColor(255,255,255,255),
1672 Applies a mask to an image
1674 else if (str_starts_with(part_of_name, "[mask:"))
1676 if (baseimg == NULL) {
1677 errorstream << "generateImage(): baseimg == NULL "
1678 << "for part_of_name=\"" << part_of_name
1679 << "\", cancelling." << std::endl;
1682 Strfnd sf(part_of_name);
1684 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1686 video::IImage *img = generateImage(filename);
1688 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1689 img->getDimension());
1692 errorstream << "generateImage(): Failed to load \""
1693 << filename << "\".";
1698 multiplys a given color to any pixel of an image
1699 color = color as ColorString
1701 else if (str_starts_with(part_of_name, "[multiply:")) {
1702 Strfnd sf(part_of_name);
1704 std::string color_str = sf.next(":");
1706 if (baseimg == NULL) {
1707 errorstream << "generateImagePart(): baseimg != NULL "
1708 << "for part_of_name=\"" << part_of_name
1709 << "\", cancelling." << std::endl;
1713 video::SColor color;
1715 if (!parseColorString(color_str, color, false))
1718 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1722 Overlays image with given color
1723 color = color as ColorString
1725 else if (str_starts_with(part_of_name, "[colorize:"))
1727 Strfnd sf(part_of_name);
1729 std::string color_str = sf.next(":");
1730 std::string ratio_str = sf.next(":");
1732 if (baseimg == NULL) {
1733 errorstream << "generateImagePart(): baseimg != NULL "
1734 << "for part_of_name=\"" << part_of_name
1735 << "\", cancelling." << std::endl;
1739 video::SColor color;
1741 bool keep_alpha = false;
1743 if (!parseColorString(color_str, color, false))
1746 if (is_number(ratio_str))
1747 ratio = mystoi(ratio_str, 0, 255);
1748 else if (ratio_str == "alpha")
1751 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1754 [applyfiltersformesh
1757 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1759 // Apply the "clean transparent" filter, if configured.
1760 if (g_settings->getBool("texture_clean_transparent"))
1761 imageCleanTransparent(baseimg, 127);
1763 /* Upscale textures to user's requested minimum size. This is a trick to make
1764 * filters look as good on low-res textures as on high-res ones, by making
1765 * low-res textures BECOME high-res ones. This is helpful for worlds that
1766 * mix high- and low-res textures, or for mods with least-common-denominator
1767 * textures that don't have the resources to offer high-res alternatives.
1769 s32 scaleto = g_settings->getS32("texture_min_size");
1771 const core::dimension2d<u32> dim = baseimg->getDimension();
1773 /* Calculate scaling needed to make the shortest texture dimension
1774 * equal to the target minimum. If e.g. this is a vertical frames
1775 * animation, the short dimension will be the real size.
1777 if ((dim.Width == 0) || (dim.Height == 0)) {
1778 errorstream << "generateImagePart(): Illegal 0 dimension "
1779 << "for part_of_name=\""<< part_of_name
1780 << "\", cancelling." << std::endl;
1783 u32 xscale = scaleto / dim.Width;
1784 u32 yscale = scaleto / dim.Height;
1785 u32 scale = (xscale > yscale) ? xscale : yscale;
1787 // Never downscale; only scale up by 2x or more.
1789 u32 w = scale * dim.Width;
1790 u32 h = scale * dim.Height;
1791 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1792 video::IImage *newimg = driver->createImage(
1793 baseimg->getColorFormat(), newdim);
1794 baseimg->copyToScaling(newimg);
1802 Resizes the base image to the given dimensions
1804 else if (str_starts_with(part_of_name, "[resize"))
1806 if (baseimg == NULL) {
1807 errorstream << "generateImagePart(): baseimg == NULL "
1808 << "for part_of_name=\""<< part_of_name
1809 << "\", cancelling." << std::endl;
1813 Strfnd sf(part_of_name);
1815 u32 width = stoi(sf.next("x"));
1816 u32 height = stoi(sf.next(""));
1817 core::dimension2d<u32> dim(width, height);
1819 video::IImage *image = RenderingEngine::get_video_driver()->
1820 createImage(video::ECF_A8R8G8B8, dim);
1821 baseimg->copyToScaling(image);
1827 Makes the base image transparent according to the given ratio.
1828 R must be between 0 and 255.
1829 0 means totally transparent.
1830 255 means totally opaque.
1832 else if (str_starts_with(part_of_name, "[opacity:")) {
1833 if (baseimg == NULL) {
1834 errorstream << "generateImagePart(): baseimg == NULL "
1835 << "for part_of_name=\"" << part_of_name
1836 << "\", cancelling." << std::endl;
1840 Strfnd sf(part_of_name);
1843 u32 ratio = mystoi(sf.next(""), 0, 255);
1845 core::dimension2d<u32> dim = baseimg->getDimension();
1847 for (u32 y = 0; y < dim.Height; y++)
1848 for (u32 x = 0; x < dim.Width; x++)
1850 video::SColor c = baseimg->getPixel(x, y);
1851 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1852 baseimg->setPixel(x, y, c);
1857 Inverts the given channels of the base image.
1858 Mode may contain the characters "r", "g", "b", "a".
1859 Only the channels that are mentioned in the mode string
1862 else if (str_starts_with(part_of_name, "[invert:")) {
1863 if (baseimg == NULL) {
1864 errorstream << "generateImagePart(): baseimg == NULL "
1865 << "for part_of_name=\"" << part_of_name
1866 << "\", cancelling." << std::endl;
1870 Strfnd sf(part_of_name);
1873 std::string mode = sf.next("");
1875 if (mode.find('a') != std::string::npos)
1876 mask |= 0xff000000UL;
1877 if (mode.find('r') != std::string::npos)
1878 mask |= 0x00ff0000UL;
1879 if (mode.find('g') != std::string::npos)
1880 mask |= 0x0000ff00UL;
1881 if (mode.find('b') != std::string::npos)
1882 mask |= 0x000000ffUL;
1884 core::dimension2d<u32> dim = baseimg->getDimension();
1886 for (u32 y = 0; y < dim.Height; y++)
1887 for (u32 x = 0; x < dim.Width; x++)
1889 video::SColor c = baseimg->getPixel(x, y);
1891 baseimg->setPixel(x, y, c);
1896 Retrieves a tile at position X,Y (in tiles)
1897 from the base image it assumes to be a
1898 tilesheet with dimensions W,H (in tiles).
1900 else if (part_of_name.substr(0,7) == "[sheet:") {
1901 if (baseimg == NULL) {
1902 errorstream << "generateImagePart(): baseimg != NULL "
1903 << "for part_of_name=\"" << part_of_name
1904 << "\", cancelling." << std::endl;
1908 Strfnd sf(part_of_name);
1910 u32 w0 = stoi(sf.next("x"));
1911 u32 h0 = stoi(sf.next(":"));
1912 u32 x0 = stoi(sf.next(","));
1913 u32 y0 = stoi(sf.next(":"));
1915 core::dimension2d<u32> img_dim = baseimg->getDimension();
1916 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1918 video::IImage *img = driver->createImage(
1919 video::ECF_A8R8G8B8, tile_dim);
1921 errorstream << "generateImagePart(): Could not create image "
1922 << "for part_of_name=\"" << part_of_name
1923 << "\", cancelling." << std::endl;
1927 img->fill(video::SColor(0,0,0,0));
1928 v2u32 vdim(tile_dim);
1929 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1930 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1931 video::SColor(255,255,255,255), NULL);
1939 errorstream << "generateImagePart(): Invalid "
1940 " modification: \"" << part_of_name << "\"" << std::endl;
1948 Draw an image on top of an another one, using the alpha channel of the
1951 This exists because IImage::copyToWithAlpha() doesn't seem to always
1954 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1955 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1957 for (u32 y0=0; y0<size.Y; y0++)
1958 for (u32 x0=0; x0<size.X; x0++)
1960 s32 src_x = src_pos.X + x0;
1961 s32 src_y = src_pos.Y + y0;
1962 s32 dst_x = dst_pos.X + x0;
1963 s32 dst_y = dst_pos.Y + y0;
1964 video::SColor src_c = src->getPixel(src_x, src_y);
1965 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1966 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1967 dst->setPixel(dst_x, dst_y, dst_c);
1972 Draw an image on top of an another one, using the alpha channel of the
1973 source image; only modify fully opaque pixels in destinaion
1975 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1976 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1978 for (u32 y0=0; y0<size.Y; y0++)
1979 for (u32 x0=0; x0<size.X; x0++)
1981 s32 src_x = src_pos.X + x0;
1982 s32 src_y = src_pos.Y + y0;
1983 s32 dst_x = dst_pos.X + x0;
1984 s32 dst_y = dst_pos.Y + y0;
1985 video::SColor src_c = src->getPixel(src_x, src_y);
1986 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1987 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1989 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1990 dst->setPixel(dst_x, dst_y, dst_c);
1995 // This function has been disabled because it is currently unused.
1996 // Feel free to re-enable if you find it handy.
1999 Draw an image on top of an another one, using the specified ratio
2000 modify all partially-opaque pixels in the destination.
2002 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2003 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2005 for (u32 y0 = 0; y0 < size.Y; y0++)
2006 for (u32 x0 = 0; x0 < size.X; x0++)
2008 s32 src_x = src_pos.X + x0;
2009 s32 src_y = src_pos.Y + y0;
2010 s32 dst_x = dst_pos.X + x0;
2011 s32 dst_y = dst_pos.Y + y0;
2012 video::SColor src_c = src->getPixel(src_x, src_y);
2013 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2014 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2017 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2019 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2020 dst->setPixel(dst_x, dst_y, dst_c);
2027 Apply color to destination
2029 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2030 const video::SColor &color, int ratio, bool keep_alpha)
2032 u32 alpha = color.getAlpha();
2033 video::SColor dst_c;
2034 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2035 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2037 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2038 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2039 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2040 if (dst_alpha > 0) {
2041 dst_c.setAlpha(dst_alpha * alpha / 255);
2042 dst->setPixel(x, y, dst_c);
2045 } else { // replace the color including the alpha
2046 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2047 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2048 if (dst->getPixel(x, y).getAlpha() > 0)
2049 dst->setPixel(x, y, color);
2051 } else { // interpolate between the color and destination
2052 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2053 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2054 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2055 dst_c = dst->getPixel(x, y);
2056 if (dst_c.getAlpha() > 0) {
2057 dst_c = color.getInterpolated(dst_c, interp);
2058 dst->setPixel(x, y, dst_c);
2065 Apply color to destination
2067 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2068 const video::SColor &color)
2070 video::SColor dst_c;
2072 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2073 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2074 dst_c = dst->getPixel(x, y);
2077 (dst_c.getRed() * color.getRed()) / 255,
2078 (dst_c.getGreen() * color.getGreen()) / 255,
2079 (dst_c.getBlue() * color.getBlue()) / 255
2081 dst->setPixel(x, y, dst_c);
2086 Apply mask to destination
2088 static void apply_mask(video::IImage *mask, video::IImage *dst,
2089 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2091 for (u32 y0 = 0; y0 < size.Y; y0++) {
2092 for (u32 x0 = 0; x0 < size.X; x0++) {
2093 s32 mask_x = x0 + mask_pos.X;
2094 s32 mask_y = y0 + mask_pos.Y;
2095 s32 dst_x = x0 + dst_pos.X;
2096 s32 dst_y = y0 + dst_pos.Y;
2097 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2098 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2099 dst_c.color &= mask_c.color;
2100 dst->setPixel(dst_x, dst_y, dst_c);
2105 static void draw_crack(video::IImage *crack, video::IImage *dst,
2106 bool use_overlay, s32 frame_count, s32 progression,
2107 video::IVideoDriver *driver)
2109 // Dimension of destination image
2110 core::dimension2d<u32> dim_dst = dst->getDimension();
2111 // Dimension of original image
2112 core::dimension2d<u32> dim_crack = crack->getDimension();
2113 // Count of crack stages
2114 s32 crack_count = dim_crack.Height / dim_crack.Width;
2115 // Limit frame_count
2116 if (frame_count > (s32) dim_dst.Height)
2117 frame_count = dim_dst.Height;
2118 if (frame_count < 1)
2120 // Limit progression
2121 if (progression > crack_count-1)
2122 progression = crack_count-1;
2123 // Dimension of a single crack stage
2124 core::dimension2d<u32> dim_crack_cropped(
2128 // Dimension of the scaled crack stage,
2129 // which is the same as the dimension of a single destination frame
2130 core::dimension2d<u32> dim_crack_scaled(
2132 dim_dst.Height / frame_count
2134 // Create cropped and scaled crack images
2135 video::IImage *crack_cropped = driver->createImage(
2136 video::ECF_A8R8G8B8, dim_crack_cropped);
2137 video::IImage *crack_scaled = driver->createImage(
2138 video::ECF_A8R8G8B8, dim_crack_scaled);
2140 if (crack_cropped && crack_scaled)
2143 v2s32 pos_crack(0, progression*dim_crack.Width);
2144 crack->copyTo(crack_cropped,
2146 core::rect<s32>(pos_crack, dim_crack_cropped));
2147 // Scale crack image by copying
2148 crack_cropped->copyToScaling(crack_scaled);
2149 // Copy or overlay crack image onto each frame
2150 for (s32 i = 0; i < frame_count; ++i)
2152 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2155 blit_with_alpha_overlay(crack_scaled, dst,
2156 v2s32(0,0), dst_pos,
2161 blit_with_alpha(crack_scaled, dst,
2162 v2s32(0,0), dst_pos,
2169 crack_scaled->drop();
2172 crack_cropped->drop();
2175 void brighten(video::IImage *image)
2180 core::dimension2d<u32> dim = image->getDimension();
2182 for (u32 y=0; y<dim.Height; y++)
2183 for (u32 x=0; x<dim.Width; x++)
2185 video::SColor c = image->getPixel(x,y);
2186 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2187 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2188 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2189 image->setPixel(x,y,c);
2193 u32 parseImageTransform(const std::string& s)
2195 int total_transform = 0;
2197 std::string transform_names[8];
2198 transform_names[0] = "i";
2199 transform_names[1] = "r90";
2200 transform_names[2] = "r180";
2201 transform_names[3] = "r270";
2202 transform_names[4] = "fx";
2203 transform_names[6] = "fy";
2205 std::size_t pos = 0;
2206 while(pos < s.size())
2209 for (int i = 0; i <= 7; ++i)
2211 const std::string &name_i = transform_names[i];
2213 if (s[pos] == ('0' + i))
2220 if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) {
2222 pos += name_i.size();
2229 // Multiply total_transform and transform in the group D4
2232 new_total = (transform + total_transform) % 4;
2234 new_total = (transform - total_transform + 8) % 4;
2235 if ((transform >= 4) ^ (total_transform >= 4))
2238 total_transform = new_total;
2240 return total_transform;
2243 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2245 if (transform % 2 == 0)
2248 return core::dimension2d<u32>(dim.Height, dim.Width);
2251 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2253 if (src == NULL || dst == NULL)
2256 core::dimension2d<u32> dstdim = dst->getDimension();
2259 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2260 assert(transform <= 7);
2263 Compute the transformation from source coordinates (sx,sy)
2264 to destination coordinates (dx,dy).
2268 if (transform == 0) // identity
2269 sxn = 0, syn = 2; // sx = dx, sy = dy
2270 else if (transform == 1) // rotate by 90 degrees ccw
2271 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2272 else if (transform == 2) // rotate by 180 degrees
2273 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2274 else if (transform == 3) // rotate by 270 degrees ccw
2275 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2276 else if (transform == 4) // flip x
2277 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2278 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2279 sxn = 2, syn = 0; // sx = dy, sy = dx
2280 else if (transform == 6) // flip y
2281 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2282 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2283 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2285 for (u32 dy=0; dy<dstdim.Height; dy++)
2286 for (u32 dx=0; dx<dstdim.Width; dx++)
2288 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2289 u32 sx = entries[sxn];
2290 u32 sy = entries[syn];
2291 video::SColor c = src->getPixel(sx,sy);
2292 dst->setPixel(dx,dy,c);
2296 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2298 if (isKnownSourceImage("override_normal.png"))
2299 return getTexture("override_normal.png");
2300 std::string fname_base = name;
2301 static const char *normal_ext = "_normal.png";
2302 static const u32 normal_ext_size = strlen(normal_ext);
2303 size_t pos = fname_base.find('.');
2304 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2305 if (isKnownSourceImage(fname_normal)) {
2306 // look for image extension and replace it
2308 while ((i = fname_base.find('.', i)) != std::string::npos) {
2309 fname_base.replace(i, 4, normal_ext);
2310 i += normal_ext_size;
2312 return getTexture(fname_base);
2317 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2319 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2320 video::SColor c(0, 0, 0, 0);
2321 video::ITexture *texture = getTexture(name);
2322 video::IImage *image = driver->createImage(texture,
2323 core::position2d<s32>(0, 0),
2324 texture->getOriginalSize());
2329 core::dimension2d<u32> dim = image->getDimension();
2332 step = dim.Width / 16;
2333 for (u16 x = 0; x < dim.Width; x += step) {
2334 for (u16 y = 0; y < dim.Width; y += step) {
2335 c = image->getPixel(x,y);
2336 if (c.getAlpha() > 0) {
2346 c.setRed(tR / total);
2347 c.setGreen(tG / total);
2348 c.setBlue(tB / total);
2355 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2357 std::string tname = "__shaderFlagsTexture";
2358 tname += normalmap_present ? "1" : "0";
2360 if (isKnownSourceImage(tname)) {
2361 return getTexture(tname);
2364 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2365 video::IImage *flags_image = driver->createImage(
2366 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2367 sanity_check(flags_image != NULL);
2368 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2369 flags_image->setPixel(0, 0, c);
2370 insertSourceImage(tname, flags_image);
2371 flags_image->drop();
2372 return getTexture(tname);