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"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
34 #include "util/strfnd.h"
35 #include "util/string.h" // for parseColorString()
36 #include "imagefilters.h"
37 #include "guiscalingfilter.h"
39 #include "renderingengine.h"
47 A cache from texture name to texture path
49 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
52 Replaces the filename extension.
54 std::string image = "a/image.png"
55 replace_ext(image, "jpg")
56 -> image = "a/image.jpg"
57 Returns true on success.
59 static bool replace_ext(std::string &path, const char *ext)
63 // Find place of last dot, fail if \ or / found.
65 for (s32 i=path.size()-1; i>=0; i--)
73 if (path[i] == '\\' || path[i] == '/')
76 // If not found, return an empty string
79 // Else make the new path
80 path = path.substr(0, last_dot_i+1) + ext;
85 Find out the full path of an image by trying different filename
90 std::string getImagePath(std::string path)
92 // A NULL-ended list of possible image extensions
93 const char *extensions[] = {
94 "png", "jpg", "bmp", "tga",
95 "pcx", "ppm", "psd", "wal", "rgb",
98 // If there is no extension, add one
99 if (removeStringEnd(path, extensions) == "")
100 path = path + ".png";
101 // Check paths until something is found to exist
102 const char **ext = extensions;
104 bool r = replace_ext(path, *ext);
107 if (fs::PathExists(path))
110 while((++ext) != NULL);
116 Gets the path to a texture by first checking if the texture exists
117 in texture_path and if not, using the data path.
119 Checks all supported extensions by replacing the original extension.
121 If not found, returns "".
123 Utilizes a thread-safe cache.
125 std::string getTexturePath(const std::string &filename)
127 std::string fullpath = "";
131 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
136 Check from texture_path
138 const std::string &texture_path = g_settings->get("texture_path");
139 if (texture_path != "") {
140 std::string testpath = texture_path + DIR_DELIM + filename;
141 // Check all filename extensions. Returns "" if not found.
142 fullpath = getImagePath(testpath);
146 Check from default data directory
150 std::string base_path = porting::path_share + DIR_DELIM + "textures"
151 + DIR_DELIM + "base" + DIR_DELIM + "pack";
152 std::string testpath = base_path + DIR_DELIM + filename;
153 // Check all filename extensions. Returns "" if not found.
154 fullpath = getImagePath(testpath);
157 // Add to cache (also an empty result is cached)
158 g_texturename_to_path_cache.set(filename, fullpath);
164 void clearTextureNameCache()
166 g_texturename_to_path_cache.clear();
170 Stores internal information about a texture.
176 video::ITexture *texture;
179 const std::string &name_,
180 video::ITexture *texture_=NULL
189 SourceImageCache: A cache used for storing source images.
192 class SourceImageCache
195 ~SourceImageCache() {
196 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
197 iter != m_images.end(); ++iter) {
198 iter->second->drop();
202 void insert(const std::string &name, video::IImage *img, bool prefer_local)
204 assert(img); // Pre-condition
206 std::map<std::string, video::IImage*>::iterator n;
207 n = m_images.find(name);
208 if (n != m_images.end()){
213 video::IImage* toadd = img;
214 bool need_to_grab = true;
216 // Try to use local texture instead if asked to
218 std::string path = getTexturePath(name);
220 video::IImage *img2 = RenderingEngine::get_video_driver()->
221 createImageFromFile(path.c_str());
224 need_to_grab = false;
231 m_images[name] = toadd;
233 video::IImage* get(const std::string &name)
235 std::map<std::string, video::IImage*>::iterator n;
236 n = m_images.find(name);
237 if (n != m_images.end())
241 // Primarily fetches from cache, secondarily tries to read from filesystem
242 video::IImage *getOrLoad(const std::string &name)
244 std::map<std::string, video::IImage*>::iterator n;
245 n = m_images.find(name);
246 if (n != m_images.end()){
247 n->second->grab(); // Grab for caller
250 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
251 std::string path = getTexturePath(name);
253 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
254 <<name<<"\""<<std::endl;
257 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
259 video::IImage *img = driver->createImageFromFile(path.c_str());
262 m_images[name] = img;
263 img->grab(); // Grab for caller
268 std::map<std::string, video::IImage*> m_images;
275 class TextureSource : public IWritableTextureSource
279 virtual ~TextureSource();
283 Now, assume a texture with the id 1 exists, and has the name
284 "stone.png^mineral1".
285 Then a random thread calls getTextureId for a texture called
286 "stone.png^mineral1^crack0".
287 ...Now, WTF should happen? Well:
288 - getTextureId strips off stuff recursively from the end until
289 the remaining part is found, or nothing is left when
290 something is stripped out
292 But it is slow to search for textures by names and modify them
294 - ContentFeatures is made to contain ids for the basic plain
296 - Crack textures can be slow by themselves, but the framework
300 - Assume a texture with the id 1 exists, and has the name
301 "stone.png^mineral_coal.png".
302 - Now getNodeTile() stumbles upon a node which uses
303 texture id 1, and determines that MATERIAL_FLAG_CRACK
304 must be applied to the tile
305 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
306 has received the current crack level 0 from the client. It
307 finds out the name of the texture with getTextureName(1),
308 appends "^crack0" to it and gets a new texture id with
309 getTextureId("stone.png^mineral_coal.png^crack0").
314 Gets a texture id from cache or
315 - if main thread, generates the texture, adds to cache and returns id.
316 - if other thread, adds to request queue and waits for main thread.
318 The id 0 points to a NULL texture. It is returned in case of error.
320 u32 getTextureId(const std::string &name);
322 // Finds out the name of a cached texture.
323 std::string getTextureName(u32 id);
326 If texture specified by the name pointed by the id doesn't
327 exist, create it, then return the cached texture.
329 Can be called from any thread. If called from some other thread
330 and not found in cache, the call is queued to the main thread
333 video::ITexture* getTexture(u32 id);
335 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
338 Get a texture specifically intended for mesh
339 application, i.e. not HUD, compositing, or other 2D
340 use. This texture may be a different size and may
341 have had additional filters applied.
343 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
345 virtual Palette* getPalette(const std::string &name);
347 bool isKnownSourceImage(const std::string &name)
349 bool is_known = false;
350 bool cache_found = m_source_image_existence.get(name, &is_known);
353 // Not found in cache; find out if a local file exists
354 is_known = (getTexturePath(name) != "");
355 m_source_image_existence.set(name, is_known);
359 // Processes queued texture requests from other threads.
360 // Shall be called from the main thread.
363 // Insert an image into the cache without touching the filesystem.
364 // Shall be called from the main thread.
365 void insertSourceImage(const std::string &name, video::IImage *img);
367 // Rebuild images and textures from the current set of source images
368 // Shall be called from the main thread.
369 void rebuildImagesAndTextures();
371 // Render a mesh to a texture.
372 // Returns NULL if render-to-texture failed.
373 // Shall be called from the main thread.
374 video::ITexture* generateTextureFromMesh(
375 const TextureFromMeshParams ¶ms);
377 video::ITexture* getNormalTexture(const std::string &name);
378 video::SColor getTextureAverageColor(const std::string &name);
379 video::ITexture *getShaderFlagsTexture(bool normamap_present);
383 // The id of the thread that is allowed to use irrlicht directly
384 std::thread::id m_main_thread;
386 // Cache of source images
387 // This should be only accessed from the main thread
388 SourceImageCache m_sourcecache;
390 // Generate a texture
391 u32 generateTexture(const std::string &name);
393 // Generate image based on a string like "stone.png" or "[crack:1:0".
394 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
395 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
397 /*! Generates an image from a full string like
398 * "stone.png^mineral_coal.png^[crack:1:0".
399 * Shall be called from the main thread.
400 * The returned Image should be dropped.
402 video::IImage* generateImage(const std::string &name);
404 // Thread-safe cache of what source images are known (true = known)
405 MutexedMap<std::string, bool> m_source_image_existence;
407 // A texture id is index in this array.
408 // The first position contains a NULL texture.
409 std::vector<TextureInfo> m_textureinfo_cache;
410 // Maps a texture name to an index in the former.
411 std::map<std::string, u32> m_name_to_id;
412 // The two former containers are behind this mutex
413 std::mutex m_textureinfo_cache_mutex;
415 // Queued texture fetches (to be processed by the main thread)
416 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
418 // Textures that have been overwritten with other ones
419 // but can't be deleted because the ITexture* might still be used
420 std::vector<video::ITexture*> m_texture_trash;
422 // Maps image file names to loaded palettes.
423 std::unordered_map<std::string, Palette> m_palettes;
425 // Cached settings needed for making textures from meshes
426 bool m_setting_trilinear_filter;
427 bool m_setting_bilinear_filter;
428 bool m_setting_anisotropic_filter;
431 IWritableTextureSource *createTextureSource()
433 return new TextureSource();
436 TextureSource::TextureSource()
438 m_main_thread = std::this_thread::get_id();
440 // Add a NULL TextureInfo as the first index, named ""
441 m_textureinfo_cache.push_back(TextureInfo(""));
442 m_name_to_id[""] = 0;
444 // Cache some settings
445 // Note: Since this is only done once, the game must be restarted
446 // for these settings to take effect
447 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
448 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
449 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
452 TextureSource::~TextureSource()
454 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
456 unsigned int textures_before = driver->getTextureCount();
458 for (std::vector<TextureInfo>::iterator iter =
459 m_textureinfo_cache.begin();
460 iter != m_textureinfo_cache.end(); ++iter)
464 driver->removeTexture(iter->texture);
466 m_textureinfo_cache.clear();
468 for (std::vector<video::ITexture*>::iterator iter =
469 m_texture_trash.begin(); iter != m_texture_trash.end();
471 video::ITexture *t = *iter;
473 //cleanup trashed texture
474 driver->removeTexture(t);
477 infostream << "~TextureSource() "<< textures_before << "/"
478 << driver->getTextureCount() << std::endl;
481 u32 TextureSource::getTextureId(const std::string &name)
483 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
487 See if texture already exists
489 MutexAutoLock lock(m_textureinfo_cache_mutex);
490 std::map<std::string, u32>::iterator n;
491 n = m_name_to_id.find(name);
492 if (n != m_name_to_id.end())
501 if (std::this_thread::get_id() == m_main_thread)
503 return generateTexture(name);
507 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
509 // We're gonna ask the result to be put into here
510 static ResultQueue<std::string, u32, u8, u8> result_queue;
512 // Throw a request in
513 m_get_texture_queue.add(name, 0, 0, &result_queue);
515 /*infostream<<"Waiting for texture from main thread, name=\""
516 <<name<<"\""<<std::endl;*/
521 // Wait result for a second
522 GetResult<std::string, u32, u8, u8>
523 result = result_queue.pop_front(1000);
525 if (result.key == name) {
530 catch(ItemNotFoundException &e)
532 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
537 infostream<<"getTextureId(): Failed"<<std::endl;
542 // Draw an image on top of an another one, using the alpha channel of the
544 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
545 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
547 // Like blit_with_alpha, but only modifies destination pixels that
549 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
550 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
553 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
554 // color alpha with the destination alpha.
555 // Otherwise, any pixels that are not fully transparent get the color alpha.
556 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
557 const video::SColor &color, int ratio, bool keep_alpha);
559 // paint a texture using the given color
560 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
561 const video::SColor &color);
563 // Apply a mask to an image
564 static void apply_mask(video::IImage *mask, video::IImage *dst,
565 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
567 // Draw or overlay a crack
568 static void draw_crack(video::IImage *crack, video::IImage *dst,
569 bool use_overlay, s32 frame_count, s32 progression,
570 video::IVideoDriver *driver);
573 void brighten(video::IImage *image);
574 // Parse a transform name
575 u32 parseImageTransform(const std::string& s);
576 // Apply transform to image dimension
577 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
578 // Apply transform to image data
579 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
582 This method generates all the textures
584 u32 TextureSource::generateTexture(const std::string &name)
586 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
588 // Empty name means texture 0
590 infostream<<"generateTexture(): name is empty"<<std::endl;
596 See if texture already exists
598 MutexAutoLock lock(m_textureinfo_cache_mutex);
599 std::map<std::string, u32>::iterator n;
600 n = m_name_to_id.find(name);
601 if (n != m_name_to_id.end()) {
607 Calling only allowed from main thread
609 if (std::this_thread::get_id() != m_main_thread) {
610 errorstream<<"TextureSource::generateTexture() "
611 "called not from main thread"<<std::endl;
615 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
616 sanity_check(driver);
618 video::IImage *img = generateImage(name);
620 video::ITexture *tex = NULL;
624 img = Align2Npot2(img, driver);
626 // Create texture from resulting image
627 tex = driver->addTexture(name.c_str(), img);
628 guiScalingCache(io::path(name.c_str()), driver, img);
633 Add texture to caches (add NULL textures too)
636 MutexAutoLock lock(m_textureinfo_cache_mutex);
638 u32 id = m_textureinfo_cache.size();
639 TextureInfo ti(name, tex);
640 m_textureinfo_cache.push_back(ti);
641 m_name_to_id[name] = id;
646 std::string TextureSource::getTextureName(u32 id)
648 MutexAutoLock lock(m_textureinfo_cache_mutex);
650 if (id >= m_textureinfo_cache.size())
652 errorstream<<"TextureSource::getTextureName(): id="<<id
653 <<" >= m_textureinfo_cache.size()="
654 <<m_textureinfo_cache.size()<<std::endl;
658 return m_textureinfo_cache[id].name;
661 video::ITexture* TextureSource::getTexture(u32 id)
663 MutexAutoLock lock(m_textureinfo_cache_mutex);
665 if (id >= m_textureinfo_cache.size())
668 return m_textureinfo_cache[id].texture;
671 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
673 u32 actual_id = getTextureId(name);
677 return getTexture(actual_id);
680 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
682 return getTexture(name + "^[applyfiltersformesh", id);
685 Palette* TextureSource::getPalette(const std::string &name)
687 // Only the main thread may load images
688 sanity_check(std::this_thread::get_id() == m_main_thread);
693 std::unordered_map<std::string, Palette>::iterator it = m_palettes.find(name);
694 if (it == m_palettes.end()) {
696 video::IImage *img = generateImage(name);
698 warningstream << "TextureSource::getPalette(): palette \"" << name
699 << "\" could not be loaded." << std::endl;
703 u32 w = img->getDimension().Width;
704 u32 h = img->getDimension().Height;
705 // Real area of the image
710 warningstream << "TextureSource::getPalette(): the specified"
711 << " palette image \"" << name << "\" is larger than 256"
712 << " pixels, using the first 256." << std::endl;
714 } else if (256 % area != 0)
715 warningstream << "TextureSource::getPalette(): the "
716 << "specified palette image \"" << name << "\" does not "
717 << "contain power of two pixels." << std::endl;
718 // We stretch the palette so it will fit 256 values
719 // This many param2 values will have the same color
720 u32 step = 256 / area;
721 // For each pixel in the image
722 for (u32 i = 0; i < area; i++) {
723 video::SColor c = img->getPixel(i % w, i / w);
724 // Fill in palette with 'step' colors
725 for (u32 j = 0; j < step; j++)
726 new_palette.push_back(c);
729 // Fill in remaining elements
730 while (new_palette.size() < 256)
731 new_palette.push_back(video::SColor(0xFFFFFFFF));
732 m_palettes[name] = new_palette;
733 it = m_palettes.find(name);
735 if (it != m_palettes.end())
736 return &((*it).second);
740 void TextureSource::processQueue()
745 //NOTE this is only thread safe for ONE consumer thread!
746 if (!m_get_texture_queue.empty())
748 GetRequest<std::string, u32, u8, u8>
749 request = m_get_texture_queue.pop();
751 /*infostream<<"TextureSource::processQueue(): "
752 <<"got texture request with "
753 <<"name=\""<<request.key<<"\""
756 m_get_texture_queue.pushResult(request, generateTexture(request.key));
760 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
762 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
764 sanity_check(std::this_thread::get_id() == m_main_thread);
766 m_sourcecache.insert(name, img, true);
767 m_source_image_existence.set(name, true);
770 void TextureSource::rebuildImagesAndTextures()
772 MutexAutoLock lock(m_textureinfo_cache_mutex);
774 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
775 sanity_check(driver);
778 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
779 TextureInfo *ti = &m_textureinfo_cache[i];
780 video::IImage *img = generateImage(ti->name);
782 img = Align2Npot2(img, driver);
784 // Create texture from resulting image
785 video::ITexture *t = NULL;
787 t = driver->addTexture(ti->name.c_str(), img);
788 guiScalingCache(io::path(ti->name.c_str()), driver, img);
791 video::ITexture *t_old = ti->texture;
796 m_texture_trash.push_back(t_old);
800 video::ITexture* TextureSource::generateTextureFromMesh(
801 const TextureFromMeshParams ¶ms)
803 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
804 sanity_check(driver);
807 const GLubyte* renderstr = glGetString(GL_RENDERER);
808 std::string renderer((char*) renderstr);
810 // use no render to texture hack
812 (renderer.find("Adreno") != std::string::npos) ||
813 (renderer.find("Mali") != std::string::npos) ||
814 (renderer.find("Immersion") != std::string::npos) ||
815 (renderer.find("Tegra") != std::string::npos) ||
816 g_settings->getBool("inventory_image_hack")
818 // Get a scene manager
819 scene::ISceneManager *smgr_main = m_device->getSceneManager();
820 sanity_check(smgr_main);
821 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
824 const float scaling = 0.2;
826 scene::IMeshSceneNode* meshnode =
827 smgr->addMeshSceneNode(params.mesh, NULL,
828 -1, v3f(0,0,0), v3f(0,0,0),
829 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
830 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
831 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
832 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
833 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
834 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
836 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
837 params.camera_position, params.camera_lookat);
838 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
839 camera->setProjectionMatrix(params.camera_projection_matrix, false);
841 smgr->setAmbientLight(params.ambient_light);
842 smgr->addLightSceneNode(0,
843 params.light_position,
845 params.light_radius*scaling);
847 core::dimension2d<u32> screen = driver->getScreenSize();
850 driver->beginScene(true, true, video::SColor(0,0,0,0));
851 driver->clearZBuffer();
854 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
856 irr::video::IImage* rawImage =
857 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
859 u8* pixels = static_cast<u8*>(rawImage->lock());
866 core::rect<s32> source(
867 screen.Width /2 - (screen.Width * (scaling / 2)),
868 screen.Height/2 - (screen.Height * (scaling / 2)),
869 screen.Width /2 + (screen.Width * (scaling / 2)),
870 screen.Height/2 + (screen.Height * (scaling / 2))
873 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
874 partsize.Width, partsize.Height, GL_RGBA,
875 GL_UNSIGNED_BYTE, pixels);
879 // Drop scene manager
882 unsigned int pixelcount = partsize.Width*partsize.Height;
885 for (unsigned int i=0; i < pixelcount; i++) {
903 video::IImage* inventory_image =
904 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
906 rawImage->copyToScaling(inventory_image);
909 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
911 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
912 inventory_image->drop();
915 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
919 driver->makeColorKeyTexture(rtt, v2s32(0,0));
921 if (params.delete_texture_on_shutdown)
922 m_texture_trash.push_back(rtt);
928 if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) {
929 static bool warned = false;
932 errorstream<<"TextureSource::generateTextureFromMesh(): "
933 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
939 // Create render target texture
940 video::ITexture *rtt = driver->addRenderTargetTexture(
941 params.dim, params.rtt_texture_name.c_str(),
942 video::ECF_A8R8G8B8);
945 errorstream<<"TextureSource::generateTextureFromMesh(): "
946 <<"addRenderTargetTexture returned NULL."<<std::endl;
951 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
952 driver->removeTexture(rtt);
953 errorstream<<"TextureSource::generateTextureFromMesh(): "
954 <<"failed to set render target"<<std::endl;
958 // Get a scene manager
959 scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager();
961 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
964 scene::IMeshSceneNode* meshnode =
965 smgr->addMeshSceneNode(params.mesh, NULL,
966 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
967 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
968 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
969 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
970 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
971 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
973 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
974 params.camera_position, params.camera_lookat);
975 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
976 camera->setProjectionMatrix(params.camera_projection_matrix, false);
978 smgr->setAmbientLight(params.ambient_light);
979 smgr->addLightSceneNode(0,
980 params.light_position,
982 params.light_radius);
985 driver->beginScene(true, true, video::SColor(0,0,0,0));
989 // Drop scene manager
992 // Unset render target
993 driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
995 if (params.delete_texture_on_shutdown)
996 m_texture_trash.push_back(rtt);
1001 video::IImage* TextureSource::generateImage(const std::string &name)
1003 // Get the base image
1005 const char separator = '^';
1006 const char escape = '\\';
1007 const char paren_open = '(';
1008 const char paren_close = ')';
1010 // Find last separator in the name
1011 s32 last_separator_pos = -1;
1013 for (s32 i = name.size() - 1; i >= 0; i--) {
1014 if (i > 0 && name[i-1] == escape)
1018 if (paren_bal == 0) {
1019 last_separator_pos = i;
1020 i = -1; // break out of loop
1024 if (paren_bal == 0) {
1025 errorstream << "generateImage(): unbalanced parentheses"
1026 << "(extranous '(') while generating texture \""
1027 << name << "\"" << std::endl;
1039 if (paren_bal > 0) {
1040 errorstream << "generateImage(): unbalanced parentheses"
1041 << "(missing matching '(') while generating texture \""
1042 << name << "\"" << std::endl;
1047 video::IImage *baseimg = NULL;
1050 If separator was found, make the base image
1051 using a recursive call.
1053 if (last_separator_pos != -1) {
1054 baseimg = generateImage(name.substr(0, last_separator_pos));
1058 Parse out the last part of the name of the image and act
1062 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1065 If this name is enclosed in parentheses, generate it
1066 and blit it onto the base image
1068 if (last_part_of_name[0] == paren_open
1069 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1070 std::string name2 = last_part_of_name.substr(1,
1071 last_part_of_name.size() - 2);
1072 video::IImage *tmp = generateImage(name2);
1074 errorstream << "generateImage(): "
1075 "Failed to generate \"" << name2 << "\""
1079 core::dimension2d<u32> dim = tmp->getDimension();
1081 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1086 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1087 // Generate image according to part of name
1088 errorstream << "generateImage(): "
1089 "Failed to generate \"" << last_part_of_name << "\""
1093 // If no resulting image, print a warning
1094 if (baseimg == NULL) {
1095 errorstream << "generateImage(): baseimg is NULL (attempted to"
1096 " create texture \"" << name << "\")" << std::endl;
1103 #include <GLES/gl.h>
1105 * Check and align image to npot2 if required by hardware
1106 * @param image image to check for npot2 alignment
1107 * @param driver driver to use for image operations
1108 * @return image or copy of image aligned to npot2
1111 inline u16 get_GL_major_version()
1113 const GLubyte *gl_version = glGetString(GL_VERSION);
1114 return (u16) (gl_version[0] - '0');
1117 video::IImage * Align2Npot2(video::IImage * image,
1118 video::IVideoDriver* driver)
1120 if (image == NULL) {
1124 core::dimension2d<u32> dim = image->getDimension();
1126 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1128 // Only GLES2 is trusted to correctly report npot support
1129 if (get_GL_major_version() > 1 &&
1130 extensions.find("GL_OES_texture_npot") != std::string::npos) {
1134 unsigned int height = npot2(dim.Height);
1135 unsigned int width = npot2(dim.Width);
1137 if ((dim.Height == height) &&
1138 (dim.Width == width)) {
1142 if (dim.Height > height) {
1146 if (dim.Width > width) {
1150 video::IImage *targetimage =
1151 driver->createImage(video::ECF_A8R8G8B8,
1152 core::dimension2d<u32>(width, height));
1154 if (targetimage != NULL) {
1155 image->copyToScaling(targetimage);
1163 static std::string unescape_string(const std::string &str, const char esc = '\\')
1166 size_t pos = 0, cpos;
1167 out.reserve(str.size());
1169 cpos = str.find_first_of(esc, pos);
1170 if (cpos == std::string::npos) {
1171 out += str.substr(pos);
1174 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1180 bool TextureSource::generateImagePart(std::string part_of_name,
1181 video::IImage *& baseimg)
1183 const char escape = '\\'; // same as in generateImage()
1184 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
1185 sanity_check(driver);
1187 // Stuff starting with [ are special commands
1188 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1190 video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
1192 image = Align2Npot2(image, driver);
1194 if (image == NULL) {
1195 if (part_of_name != "") {
1197 // Do not create normalmap dummies
1198 if (part_of_name.find("_normal.png") != std::string::npos) {
1199 warningstream << "generateImage(): Could not load normal map \""
1200 << part_of_name << "\"" << std::endl;
1204 errorstream << "generateImage(): Could not load image \""
1205 << part_of_name << "\" while building texture; "
1206 "Creating a dummy image" << std::endl;
1209 // Just create a dummy image
1210 //core::dimension2d<u32> dim(2,2);
1211 core::dimension2d<u32> dim(1,1);
1212 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1213 sanity_check(image != NULL);
1214 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1215 image->setPixel(1,0, video::SColor(255,0,255,0));
1216 image->setPixel(0,1, video::SColor(255,0,0,255));
1217 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1218 image->setPixel(0,0, video::SColor(255,myrand()%256,
1219 myrand()%256,myrand()%256));
1220 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1221 myrand()%256,myrand()%256));
1222 image->setPixel(0,1, video::SColor(255,myrand()%256,
1223 myrand()%256,myrand()%256));
1224 image->setPixel(1,1, video::SColor(255,myrand()%256,
1225 myrand()%256,myrand()%256));*/
1228 // If base image is NULL, load as base.
1229 if (baseimg == NULL)
1231 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1233 Copy it this way to get an alpha channel.
1234 Otherwise images with alpha cannot be blitted on
1235 images that don't have alpha in the original file.
1237 core::dimension2d<u32> dim = image->getDimension();
1238 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1239 image->copyTo(baseimg);
1241 // Else blit on base.
1244 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1245 // Size of the copied area
1246 core::dimension2d<u32> dim = image->getDimension();
1247 //core::dimension2d<u32> dim(16,16);
1248 // Position to copy the blitted to in the base image
1249 core::position2d<s32> pos_to(0,0);
1250 // Position to copy the blitted from in the blitted image
1251 core::position2d<s32> pos_from(0,0);
1253 /*image->copyToWithAlpha(baseimg, pos_to,
1254 core::rect<s32>(pos_from, dim),
1255 video::SColor(255,255,255,255),
1258 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1259 if (dim == dim_dst) {
1260 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1261 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1262 // Upscale overlying image
1263 video::IImage *scaled_image = RenderingEngine::get_video_driver()->
1264 createImage(video::ECF_A8R8G8B8, dim_dst);
1265 image->copyToScaling(scaled_image);
1267 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1268 scaled_image->drop();
1270 // Upscale base image
1271 video::IImage *scaled_base = RenderingEngine::get_video_driver()->
1272 createImage(video::ECF_A8R8G8B8, dim);
1273 baseimg->copyToScaling(scaled_base);
1275 baseimg = scaled_base;
1277 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1285 // A special texture modification
1287 /*infostream<<"generateImage(): generating special "
1288 <<"modification \""<<part_of_name<<"\""
1294 Adds a cracking texture
1295 N = animation frame count, P = crack progression
1297 if (str_starts_with(part_of_name, "[crack"))
1299 if (baseimg == NULL) {
1300 errorstream<<"generateImagePart(): baseimg == NULL "
1301 <<"for part_of_name=\""<<part_of_name
1302 <<"\", cancelling."<<std::endl;
1306 // Crack image number and overlay option
1307 bool use_overlay = (part_of_name[6] == 'o');
1308 Strfnd sf(part_of_name);
1310 s32 frame_count = stoi(sf.next(":"));
1311 s32 progression = stoi(sf.next(":"));
1313 if (progression >= 0) {
1317 It is an image with a number of cracking stages
1320 video::IImage *img_crack = m_sourcecache.getOrLoad(
1321 "crack_anylength.png");
1324 draw_crack(img_crack, baseimg,
1325 use_overlay, frame_count,
1326 progression, driver);
1332 [combine:WxH:X,Y=filename:X,Y=filename2
1333 Creates a bigger texture from any amount of smaller ones
1335 else if (str_starts_with(part_of_name, "[combine"))
1337 Strfnd sf(part_of_name);
1339 u32 w0 = stoi(sf.next("x"));
1340 u32 h0 = stoi(sf.next(":"));
1341 core::dimension2d<u32> dim(w0,h0);
1342 if (baseimg == NULL) {
1343 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1344 baseimg->fill(video::SColor(0,0,0,0));
1346 while (sf.at_end() == false) {
1347 u32 x = stoi(sf.next(","));
1348 u32 y = stoi(sf.next("="));
1349 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1350 infostream<<"Adding \""<<filename
1351 <<"\" to combined ("<<x<<","<<y<<")"
1353 video::IImage *img = generateImage(filename);
1355 core::dimension2d<u32> dim = img->getDimension();
1356 infostream<<"Size "<<dim.Width
1357 <<"x"<<dim.Height<<std::endl;
1358 core::position2d<s32> pos_base(x, y);
1359 video::IImage *img2 =
1360 driver->createImage(video::ECF_A8R8G8B8, dim);
1363 /*img2->copyToWithAlpha(baseimg, pos_base,
1364 core::rect<s32>(v2s32(0,0), dim),
1365 video::SColor(255,255,255,255),
1367 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1370 errorstream << "generateImagePart(): Failed to load image \""
1371 << filename << "\" for [combine" << std::endl;
1378 else if (str_starts_with(part_of_name, "[brighten"))
1380 if (baseimg == NULL) {
1381 errorstream<<"generateImagePart(): baseimg==NULL "
1382 <<"for part_of_name=\""<<part_of_name
1383 <<"\", cancelling."<<std::endl;
1391 Make image completely opaque.
1392 Used for the leaves texture when in old leaves mode, so
1393 that the transparent parts don't look completely black
1394 when simple alpha channel is used for rendering.
1396 else if (str_starts_with(part_of_name, "[noalpha"))
1398 if (baseimg == NULL){
1399 errorstream<<"generateImagePart(): baseimg==NULL "
1400 <<"for part_of_name=\""<<part_of_name
1401 <<"\", cancelling."<<std::endl;
1405 core::dimension2d<u32> dim = baseimg->getDimension();
1407 // Set alpha to full
1408 for (u32 y=0; y<dim.Height; y++)
1409 for (u32 x=0; x<dim.Width; x++)
1411 video::SColor c = baseimg->getPixel(x,y);
1413 baseimg->setPixel(x,y,c);
1418 Convert one color to transparent.
1420 else if (str_starts_with(part_of_name, "[makealpha:"))
1422 if (baseimg == NULL) {
1423 errorstream<<"generateImagePart(): baseimg == NULL "
1424 <<"for part_of_name=\""<<part_of_name
1425 <<"\", cancelling."<<std::endl;
1429 Strfnd sf(part_of_name.substr(11));
1430 u32 r1 = stoi(sf.next(","));
1431 u32 g1 = stoi(sf.next(","));
1432 u32 b1 = stoi(sf.next(""));
1434 core::dimension2d<u32> dim = baseimg->getDimension();
1436 /*video::IImage *oldbaseimg = baseimg;
1437 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1438 oldbaseimg->copyTo(baseimg);
1439 oldbaseimg->drop();*/
1441 // Set alpha to full
1442 for (u32 y=0; y<dim.Height; y++)
1443 for (u32 x=0; x<dim.Width; x++)
1445 video::SColor c = baseimg->getPixel(x,y);
1447 u32 g = c.getGreen();
1448 u32 b = c.getBlue();
1449 if (!(r == r1 && g == g1 && b == b1))
1452 baseimg->setPixel(x,y,c);
1457 Rotates and/or flips the image.
1459 N can be a number (between 0 and 7) or a transform name.
1460 Rotations are counter-clockwise.
1462 1 R90 rotate by 90 degrees
1463 2 R180 rotate by 180 degrees
1464 3 R270 rotate by 270 degrees
1466 5 FXR90 flip X then rotate by 90 degrees
1468 7 FYR90 flip Y then rotate by 90 degrees
1470 Note: Transform names can be concatenated to produce
1471 their product (applies the first then the second).
1472 The resulting transform will be equivalent to one of the
1473 eight existing ones, though (see: dihedral group).
1475 else if (str_starts_with(part_of_name, "[transform"))
1477 if (baseimg == NULL) {
1478 errorstream<<"generateImagePart(): baseimg == NULL "
1479 <<"for part_of_name=\""<<part_of_name
1480 <<"\", cancelling."<<std::endl;
1484 u32 transform = parseImageTransform(part_of_name.substr(10));
1485 core::dimension2d<u32> dim = imageTransformDimension(
1486 transform, baseimg->getDimension());
1487 video::IImage *image = driver->createImage(
1488 baseimg->getColorFormat(), dim);
1489 sanity_check(image != NULL);
1490 imageTransform(transform, baseimg, image);
1495 [inventorycube{topimage{leftimage{rightimage
1496 In every subimage, replace ^ with &.
1497 Create an "inventory cube".
1498 NOTE: This should be used only on its own.
1499 Example (a grass block (not actually used in game):
1500 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1502 else if (str_starts_with(part_of_name, "[inventorycube"))
1504 if (baseimg != NULL){
1505 errorstream<<"generateImagePart(): baseimg != NULL "
1506 <<"for part_of_name=\""<<part_of_name
1507 <<"\", cancelling."<<std::endl;
1511 str_replace(part_of_name, '&', '^');
1512 Strfnd sf(part_of_name);
1514 std::string imagename_top = sf.next("{");
1515 std::string imagename_left = sf.next("{");
1516 std::string imagename_right = sf.next("{");
1518 // Generate images for the faces of the cube
1519 video::IImage *img_top = generateImage(imagename_top);
1520 video::IImage *img_left = generateImage(imagename_left);
1521 video::IImage *img_right = generateImage(imagename_right);
1523 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1524 errorstream << "generateImagePart(): Failed to create textures"
1525 << " for inventorycube \"" << part_of_name << "\""
1527 baseimg = generateImage(imagename_top);
1532 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1533 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1535 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1536 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1538 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1539 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1542 // Create textures from images
1543 video::ITexture *texture_top = driver->addTexture(
1544 (imagename_top + "__temp__").c_str(), img_top);
1545 video::ITexture *texture_left = driver->addTexture(
1546 (imagename_left + "__temp__").c_str(), img_left);
1547 video::ITexture *texture_right = driver->addTexture(
1548 (imagename_right + "__temp__").c_str(), img_right);
1549 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1557 Draw a cube mesh into a render target texture
1559 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1560 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1561 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1562 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1563 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1564 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1565 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1566 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1568 TextureFromMeshParams params;
1570 params.dim.set(64, 64);
1571 params.rtt_texture_name = part_of_name + "_RTT";
1572 // We will delete the rtt texture ourselves
1573 params.delete_texture_on_shutdown = false;
1574 params.camera_position.set(0, 1.0, -1.5);
1575 params.camera_position.rotateXZBy(45);
1576 params.camera_lookat.set(0, 0, 0);
1577 // Set orthogonal projection
1578 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1579 1.65, 1.65, 0, 100);
1581 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1582 params.light_position.set(10, 100, -50);
1583 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1584 params.light_radius = 1000;
1586 video::ITexture *rtt = generateTextureFromMesh(params);
1592 driver->removeTexture(texture_top);
1593 driver->removeTexture(texture_left);
1594 driver->removeTexture(texture_right);
1597 baseimg = generateImage(imagename_top);
1601 // Create image of render target
1602 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1603 FATAL_ERROR_IF(!image, "Could not create image of render target");
1606 driver->removeTexture(rtt);
1608 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1611 image->copyTo(baseimg);
1616 [lowpart:percent:filename
1617 Adds the lower part of a texture
1619 else if (str_starts_with(part_of_name, "[lowpart:"))
1621 Strfnd sf(part_of_name);
1623 u32 percent = stoi(sf.next(":"));
1624 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1626 if (baseimg == NULL)
1627 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1628 video::IImage *img = generateImage(filename);
1631 core::dimension2d<u32> dim = img->getDimension();
1632 core::position2d<s32> pos_base(0, 0);
1633 video::IImage *img2 =
1634 driver->createImage(video::ECF_A8R8G8B8, dim);
1637 core::position2d<s32> clippos(0, 0);
1638 clippos.Y = dim.Height * (100-percent) / 100;
1639 core::dimension2d<u32> clipdim = dim;
1640 clipdim.Height = clipdim.Height * percent / 100 + 1;
1641 core::rect<s32> cliprect(clippos, clipdim);
1642 img2->copyToWithAlpha(baseimg, pos_base,
1643 core::rect<s32>(v2s32(0,0), dim),
1644 video::SColor(255,255,255,255),
1651 Crops a frame of a vertical animation.
1652 N = frame count, I = frame index
1654 else if (str_starts_with(part_of_name, "[verticalframe:"))
1656 Strfnd sf(part_of_name);
1658 u32 frame_count = stoi(sf.next(":"));
1659 u32 frame_index = stoi(sf.next(":"));
1661 if (baseimg == NULL){
1662 errorstream<<"generateImagePart(): baseimg != NULL "
1663 <<"for part_of_name=\""<<part_of_name
1664 <<"\", cancelling."<<std::endl;
1668 v2u32 frame_size = baseimg->getDimension();
1669 frame_size.Y /= frame_count;
1671 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1674 errorstream<<"generateImagePart(): Could not create image "
1675 <<"for part_of_name=\""<<part_of_name
1676 <<"\", cancelling."<<std::endl;
1680 // Fill target image with transparency
1681 img->fill(video::SColor(0,0,0,0));
1683 core::dimension2d<u32> dim = frame_size;
1684 core::position2d<s32> pos_dst(0, 0);
1685 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1686 baseimg->copyToWithAlpha(img, pos_dst,
1687 core::rect<s32>(pos_src, dim),
1688 video::SColor(255,255,255,255),
1696 Applies a mask to an image
1698 else if (str_starts_with(part_of_name, "[mask:"))
1700 if (baseimg == NULL) {
1701 errorstream << "generateImage(): baseimg == NULL "
1702 << "for part_of_name=\"" << part_of_name
1703 << "\", cancelling." << std::endl;
1706 Strfnd sf(part_of_name);
1708 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1710 video::IImage *img = generateImage(filename);
1712 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1713 img->getDimension());
1716 errorstream << "generateImage(): Failed to load \""
1717 << filename << "\".";
1722 multiplys a given color to any pixel of an image
1723 color = color as ColorString
1725 else if (str_starts_with(part_of_name, "[multiply:")) {
1726 Strfnd sf(part_of_name);
1728 std::string color_str = sf.next(":");
1730 if (baseimg == NULL) {
1731 errorstream << "generateImagePart(): baseimg != NULL "
1732 << "for part_of_name=\"" << part_of_name
1733 << "\", cancelling." << std::endl;
1737 video::SColor color;
1739 if (!parseColorString(color_str, color, false))
1742 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1746 Overlays image with given color
1747 color = color as ColorString
1749 else if (str_starts_with(part_of_name, "[colorize:"))
1751 Strfnd sf(part_of_name);
1753 std::string color_str = sf.next(":");
1754 std::string ratio_str = sf.next(":");
1756 if (baseimg == NULL) {
1757 errorstream << "generateImagePart(): baseimg != NULL "
1758 << "for part_of_name=\"" << part_of_name
1759 << "\", cancelling." << std::endl;
1763 video::SColor color;
1765 bool keep_alpha = false;
1767 if (!parseColorString(color_str, color, false))
1770 if (is_number(ratio_str))
1771 ratio = mystoi(ratio_str, 0, 255);
1772 else if (ratio_str == "alpha")
1775 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1778 [applyfiltersformesh
1781 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1783 // Apply the "clean transparent" filter, if configured.
1784 if (g_settings->getBool("texture_clean_transparent"))
1785 imageCleanTransparent(baseimg, 127);
1787 /* Upscale textures to user's requested minimum size. This is a trick to make
1788 * filters look as good on low-res textures as on high-res ones, by making
1789 * low-res textures BECOME high-res ones. This is helpful for worlds that
1790 * mix high- and low-res textures, or for mods with least-common-denominator
1791 * textures that don't have the resources to offer high-res alternatives.
1793 s32 scaleto = g_settings->getS32("texture_min_size");
1795 const core::dimension2d<u32> dim = baseimg->getDimension();
1797 /* Calculate scaling needed to make the shortest texture dimension
1798 * equal to the target minimum. If e.g. this is a vertical frames
1799 * animation, the short dimension will be the real size.
1801 if ((dim.Width == 0) || (dim.Height == 0)) {
1802 errorstream << "generateImagePart(): Illegal 0 dimension "
1803 << "for part_of_name=\""<< part_of_name
1804 << "\", cancelling." << std::endl;
1807 u32 xscale = scaleto / dim.Width;
1808 u32 yscale = scaleto / dim.Height;
1809 u32 scale = (xscale > yscale) ? xscale : yscale;
1811 // Never downscale; only scale up by 2x or more.
1813 u32 w = scale * dim.Width;
1814 u32 h = scale * dim.Height;
1815 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1816 video::IImage *newimg = driver->createImage(
1817 baseimg->getColorFormat(), newdim);
1818 baseimg->copyToScaling(newimg);
1826 Resizes the base image to the given dimensions
1828 else if (str_starts_with(part_of_name, "[resize"))
1830 if (baseimg == NULL) {
1831 errorstream << "generateImagePart(): baseimg == NULL "
1832 << "for part_of_name=\""<< part_of_name
1833 << "\", cancelling." << std::endl;
1837 Strfnd sf(part_of_name);
1839 u32 width = stoi(sf.next("x"));
1840 u32 height = stoi(sf.next(""));
1841 core::dimension2d<u32> dim(width, height);
1843 video::IImage *image = RenderingEngine::get_video_driver()->
1844 createImage(video::ECF_A8R8G8B8, dim);
1845 baseimg->copyToScaling(image);
1851 Makes the base image transparent according to the given ratio.
1852 R must be between 0 and 255.
1853 0 means totally transparent.
1854 255 means totally opaque.
1856 else if (str_starts_with(part_of_name, "[opacity:")) {
1857 if (baseimg == NULL) {
1858 errorstream << "generateImagePart(): baseimg == NULL "
1859 << "for part_of_name=\"" << part_of_name
1860 << "\", cancelling." << std::endl;
1864 Strfnd sf(part_of_name);
1867 u32 ratio = mystoi(sf.next(""), 0, 255);
1869 core::dimension2d<u32> dim = baseimg->getDimension();
1871 for (u32 y = 0; y < dim.Height; y++)
1872 for (u32 x = 0; x < dim.Width; x++)
1874 video::SColor c = baseimg->getPixel(x, y);
1875 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1876 baseimg->setPixel(x, y, c);
1881 Inverts the given channels of the base image.
1882 Mode may contain the characters "r", "g", "b", "a".
1883 Only the channels that are mentioned in the mode string
1886 else if (str_starts_with(part_of_name, "[invert:")) {
1887 if (baseimg == NULL) {
1888 errorstream << "generateImagePart(): baseimg == NULL "
1889 << "for part_of_name=\"" << part_of_name
1890 << "\", cancelling." << std::endl;
1894 Strfnd sf(part_of_name);
1897 std::string mode = sf.next("");
1899 if (mode.find("a") != std::string::npos)
1900 mask |= 0xff000000UL;
1901 if (mode.find("r") != std::string::npos)
1902 mask |= 0x00ff0000UL;
1903 if (mode.find("g") != std::string::npos)
1904 mask |= 0x0000ff00UL;
1905 if (mode.find("b") != std::string::npos)
1906 mask |= 0x000000ffUL;
1908 core::dimension2d<u32> dim = baseimg->getDimension();
1910 for (u32 y = 0; y < dim.Height; y++)
1911 for (u32 x = 0; x < dim.Width; x++)
1913 video::SColor c = baseimg->getPixel(x, y);
1915 baseimg->setPixel(x, y, c);
1920 Retrieves a tile at position X,Y (in tiles)
1921 from the base image it assumes to be a
1922 tilesheet with dimensions W,H (in tiles).
1924 else if (part_of_name.substr(0,7) == "[sheet:") {
1925 if (baseimg == NULL) {
1926 errorstream << "generateImagePart(): baseimg != NULL "
1927 << "for part_of_name=\"" << part_of_name
1928 << "\", cancelling." << std::endl;
1932 Strfnd sf(part_of_name);
1934 u32 w0 = stoi(sf.next("x"));
1935 u32 h0 = stoi(sf.next(":"));
1936 u32 x0 = stoi(sf.next(","));
1937 u32 y0 = stoi(sf.next(":"));
1939 core::dimension2d<u32> img_dim = baseimg->getDimension();
1940 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1942 video::IImage *img = driver->createImage(
1943 video::ECF_A8R8G8B8, tile_dim);
1945 errorstream << "generateImagePart(): Could not create image "
1946 << "for part_of_name=\"" << part_of_name
1947 << "\", cancelling." << std::endl;
1951 img->fill(video::SColor(0,0,0,0));
1952 v2u32 vdim(tile_dim);
1953 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1954 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1955 video::SColor(255,255,255,255), NULL);
1963 errorstream << "generateImagePart(): Invalid "
1964 " modification: \"" << part_of_name << "\"" << std::endl;
1972 Draw an image on top of an another one, using the alpha channel of the
1975 This exists because IImage::copyToWithAlpha() doesn't seem to always
1978 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1979 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1981 for (u32 y0=0; y0<size.Y; y0++)
1982 for (u32 x0=0; x0<size.X; x0++)
1984 s32 src_x = src_pos.X + x0;
1985 s32 src_y = src_pos.Y + y0;
1986 s32 dst_x = dst_pos.X + x0;
1987 s32 dst_y = dst_pos.Y + y0;
1988 video::SColor src_c = src->getPixel(src_x, src_y);
1989 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1990 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1991 dst->setPixel(dst_x, dst_y, dst_c);
1996 Draw an image on top of an another one, using the alpha channel of the
1997 source image; only modify fully opaque pixels in destinaion
1999 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
2000 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
2002 for (u32 y0=0; y0<size.Y; y0++)
2003 for (u32 x0=0; x0<size.X; x0++)
2005 s32 src_x = src_pos.X + x0;
2006 s32 src_y = src_pos.Y + y0;
2007 s32 dst_x = dst_pos.X + x0;
2008 s32 dst_y = dst_pos.Y + y0;
2009 video::SColor src_c = src->getPixel(src_x, src_y);
2010 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2011 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
2013 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2014 dst->setPixel(dst_x, dst_y, dst_c);
2019 // This function has been disabled because it is currently unused.
2020 // Feel free to re-enable if you find it handy.
2023 Draw an image on top of an another one, using the specified ratio
2024 modify all partially-opaque pixels in the destination.
2026 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2027 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2029 for (u32 y0 = 0; y0 < size.Y; y0++)
2030 for (u32 x0 = 0; x0 < size.X; x0++)
2032 s32 src_x = src_pos.X + x0;
2033 s32 src_y = src_pos.Y + y0;
2034 s32 dst_x = dst_pos.X + x0;
2035 s32 dst_y = dst_pos.Y + y0;
2036 video::SColor src_c = src->getPixel(src_x, src_y);
2037 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2038 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2041 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2043 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2044 dst->setPixel(dst_x, dst_y, dst_c);
2051 Apply color to destination
2053 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2054 const video::SColor &color, int ratio, bool keep_alpha)
2056 u32 alpha = color.getAlpha();
2057 video::SColor dst_c;
2058 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2059 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2061 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2062 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2063 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2064 if (dst_alpha > 0) {
2065 dst_c.setAlpha(dst_alpha * alpha / 255);
2066 dst->setPixel(x, y, dst_c);
2069 } else { // replace the color including the alpha
2070 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2071 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2072 if (dst->getPixel(x, y).getAlpha() > 0)
2073 dst->setPixel(x, y, color);
2075 } else { // interpolate between the color and destination
2076 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2077 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2078 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2079 dst_c = dst->getPixel(x, y);
2080 if (dst_c.getAlpha() > 0) {
2081 dst_c = color.getInterpolated(dst_c, interp);
2082 dst->setPixel(x, y, dst_c);
2089 Apply color to destination
2091 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2092 const video::SColor &color)
2094 video::SColor dst_c;
2096 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2097 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2098 dst_c = dst->getPixel(x, y);
2101 (dst_c.getRed() * color.getRed()) / 255,
2102 (dst_c.getGreen() * color.getGreen()) / 255,
2103 (dst_c.getBlue() * color.getBlue()) / 255
2105 dst->setPixel(x, y, dst_c);
2110 Apply mask to destination
2112 static void apply_mask(video::IImage *mask, video::IImage *dst,
2113 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2115 for (u32 y0 = 0; y0 < size.Y; y0++) {
2116 for (u32 x0 = 0; x0 < size.X; x0++) {
2117 s32 mask_x = x0 + mask_pos.X;
2118 s32 mask_y = y0 + mask_pos.Y;
2119 s32 dst_x = x0 + dst_pos.X;
2120 s32 dst_y = y0 + dst_pos.Y;
2121 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2122 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2123 dst_c.color &= mask_c.color;
2124 dst->setPixel(dst_x, dst_y, dst_c);
2129 static void draw_crack(video::IImage *crack, video::IImage *dst,
2130 bool use_overlay, s32 frame_count, s32 progression,
2131 video::IVideoDriver *driver)
2133 // Dimension of destination image
2134 core::dimension2d<u32> dim_dst = dst->getDimension();
2135 // Dimension of original image
2136 core::dimension2d<u32> dim_crack = crack->getDimension();
2137 // Count of crack stages
2138 s32 crack_count = dim_crack.Height / dim_crack.Width;
2139 // Limit frame_count
2140 if (frame_count > (s32) dim_dst.Height)
2141 frame_count = dim_dst.Height;
2142 if (frame_count < 1)
2144 // Limit progression
2145 if (progression > crack_count-1)
2146 progression = crack_count-1;
2147 // Dimension of a single crack stage
2148 core::dimension2d<u32> dim_crack_cropped(
2152 // Dimension of the scaled crack stage,
2153 // which is the same as the dimension of a single destination frame
2154 core::dimension2d<u32> dim_crack_scaled(
2156 dim_dst.Height / frame_count
2158 // Create cropped and scaled crack images
2159 video::IImage *crack_cropped = driver->createImage(
2160 video::ECF_A8R8G8B8, dim_crack_cropped);
2161 video::IImage *crack_scaled = driver->createImage(
2162 video::ECF_A8R8G8B8, dim_crack_scaled);
2164 if (crack_cropped && crack_scaled)
2167 v2s32 pos_crack(0, progression*dim_crack.Width);
2168 crack->copyTo(crack_cropped,
2170 core::rect<s32>(pos_crack, dim_crack_cropped));
2171 // Scale crack image by copying
2172 crack_cropped->copyToScaling(crack_scaled);
2173 // Copy or overlay crack image onto each frame
2174 for (s32 i = 0; i < frame_count; ++i)
2176 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2179 blit_with_alpha_overlay(crack_scaled, dst,
2180 v2s32(0,0), dst_pos,
2185 blit_with_alpha(crack_scaled, dst,
2186 v2s32(0,0), dst_pos,
2193 crack_scaled->drop();
2196 crack_cropped->drop();
2199 void brighten(video::IImage *image)
2204 core::dimension2d<u32> dim = image->getDimension();
2206 for (u32 y=0; y<dim.Height; y++)
2207 for (u32 x=0; x<dim.Width; x++)
2209 video::SColor c = image->getPixel(x,y);
2210 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2211 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2212 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2213 image->setPixel(x,y,c);
2217 u32 parseImageTransform(const std::string& s)
2219 int total_transform = 0;
2221 std::string transform_names[8];
2222 transform_names[0] = "i";
2223 transform_names[1] = "r90";
2224 transform_names[2] = "r180";
2225 transform_names[3] = "r270";
2226 transform_names[4] = "fx";
2227 transform_names[6] = "fy";
2229 std::size_t pos = 0;
2230 while(pos < s.size())
2233 for (int i = 0; i <= 7; ++i)
2235 const std::string &name_i = transform_names[i];
2237 if (s[pos] == ('0' + i))
2243 else if (!(name_i.empty()) &&
2244 lowercase(s.substr(pos, name_i.size())) == name_i)
2247 pos += name_i.size();
2254 // Multiply total_transform and transform in the group D4
2257 new_total = (transform + total_transform) % 4;
2259 new_total = (transform - total_transform + 8) % 4;
2260 if ((transform >= 4) ^ (total_transform >= 4))
2263 total_transform = new_total;
2265 return total_transform;
2268 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2270 if (transform % 2 == 0)
2273 return core::dimension2d<u32>(dim.Height, dim.Width);
2276 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2278 if (src == NULL || dst == NULL)
2281 core::dimension2d<u32> dstdim = dst->getDimension();
2284 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2285 assert(transform <= 7);
2288 Compute the transformation from source coordinates (sx,sy)
2289 to destination coordinates (dx,dy).
2293 if (transform == 0) // identity
2294 sxn = 0, syn = 2; // sx = dx, sy = dy
2295 else if (transform == 1) // rotate by 90 degrees ccw
2296 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2297 else if (transform == 2) // rotate by 180 degrees
2298 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2299 else if (transform == 3) // rotate by 270 degrees ccw
2300 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2301 else if (transform == 4) // flip x
2302 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2303 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2304 sxn = 2, syn = 0; // sx = dy, sy = dx
2305 else if (transform == 6) // flip y
2306 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2307 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2308 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2310 for (u32 dy=0; dy<dstdim.Height; dy++)
2311 for (u32 dx=0; dx<dstdim.Width; dx++)
2313 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2314 u32 sx = entries[sxn];
2315 u32 sy = entries[syn];
2316 video::SColor c = src->getPixel(sx,sy);
2317 dst->setPixel(dx,dy,c);
2321 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2323 if (isKnownSourceImage("override_normal.png"))
2324 return getTexture("override_normal.png");
2325 std::string fname_base = name;
2326 static const char *normal_ext = "_normal.png";
2327 static const u32 normal_ext_size = strlen(normal_ext);
2328 size_t pos = fname_base.find(".");
2329 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2330 if (isKnownSourceImage(fname_normal)) {
2331 // look for image extension and replace it
2333 while ((i = fname_base.find(".", i)) != std::string::npos) {
2334 fname_base.replace(i, 4, normal_ext);
2335 i += normal_ext_size;
2337 return getTexture(fname_base);
2342 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2344 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2345 video::SColor c(0, 0, 0, 0);
2346 video::ITexture *texture = getTexture(name);
2347 video::IImage *image = driver->createImage(texture,
2348 core::position2d<s32>(0, 0),
2349 texture->getOriginalSize());
2354 core::dimension2d<u32> dim = image->getDimension();
2357 step = dim.Width / 16;
2358 for (u16 x = 0; x < dim.Width; x += step) {
2359 for (u16 y = 0; y < dim.Width; y += step) {
2360 c = image->getPixel(x,y);
2361 if (c.getAlpha() > 0) {
2371 c.setRed(tR / total);
2372 c.setGreen(tG / total);
2373 c.setBlue(tB / total);
2380 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2382 std::string tname = "__shaderFlagsTexture";
2383 tname += normalmap_present ? "1" : "0";
2385 if (isKnownSourceImage(tname)) {
2386 return getTexture(tname);
2388 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
2389 video::IImage *flags_image = driver->createImage(
2390 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2391 sanity_check(flags_image != NULL);
2392 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2393 flags_image->setPixel(0, 0, c);
2394 insertSourceImage(tname, flags_image);
2395 flags_image->drop();
2396 return getTexture(tname);