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"
46 A cache from texture name to texture path
48 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
51 Replaces the filename extension.
53 std::string image = "a/image.png"
54 replace_ext(image, "jpg")
55 -> image = "a/image.jpg"
56 Returns true on success.
58 static bool replace_ext(std::string &path, const char *ext)
62 // Find place of last dot, fail if \ or / found.
64 for (s32 i=path.size()-1; i>=0; i--)
72 if (path[i] == '\\' || path[i] == '/')
75 // If not found, return an empty string
78 // Else make the new path
79 path = path.substr(0, last_dot_i+1) + ext;
84 Find out the full path of an image by trying different filename
89 std::string getImagePath(std::string path)
91 // A NULL-ended list of possible image extensions
92 const char *extensions[] = {
93 "png", "jpg", "bmp", "tga",
94 "pcx", "ppm", "psd", "wal", "rgb",
97 // If there is no extension, add one
98 if (removeStringEnd(path, extensions) == "")
100 // Check paths until something is found to exist
101 const char **ext = extensions;
103 bool r = replace_ext(path, *ext);
106 if (fs::PathExists(path))
109 while((++ext) != NULL);
115 Gets the path to a texture by first checking if the texture exists
116 in texture_path and if not, using the data path.
118 Checks all supported extensions by replacing the original extension.
120 If not found, returns "".
122 Utilizes a thread-safe cache.
124 std::string getTexturePath(const std::string &filename)
126 std::string fullpath = "";
130 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
135 Check from texture_path
137 std::string texture_path = g_settings->get("texture_path");
138 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,
203 bool prefer_local, video::IVideoDriver *driver)
205 assert(img); // Pre-condition
207 std::map<std::string, video::IImage*>::iterator n;
208 n = m_images.find(name);
209 if (n != m_images.end()){
214 video::IImage* toadd = img;
215 bool need_to_grab = true;
217 // Try to use local texture instead if asked to
219 std::string path = getTexturePath(name);
221 video::IImage *img2 = driver->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, IrrlichtDevice *device)
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 = device->getVideoDriver();
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
278 TextureSource(IrrlichtDevice *device);
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 // Returns a pointer to the irrlicht device
346 virtual IrrlichtDevice* getDevice()
351 bool isKnownSourceImage(const std::string &name)
353 bool is_known = false;
354 bool cache_found = m_source_image_existence.get(name, &is_known);
357 // Not found in cache; find out if a local file exists
358 is_known = (getTexturePath(name) != "");
359 m_source_image_existence.set(name, is_known);
363 // Processes queued texture requests from other threads.
364 // Shall be called from the main thread.
367 // Insert an image into the cache without touching the filesystem.
368 // Shall be called from the main thread.
369 void insertSourceImage(const std::string &name, video::IImage *img);
371 // Rebuild images and textures from the current set of source images
372 // Shall be called from the main thread.
373 void rebuildImagesAndTextures();
375 // Render a mesh to a texture.
376 // Returns NULL if render-to-texture failed.
377 // Shall be called from the main thread.
378 video::ITexture* generateTextureFromMesh(
379 const TextureFromMeshParams ¶ms);
381 // Generates an image from a full string like
382 // "stone.png^mineral_coal.png^[crack:1:0".
383 // Shall be called from the main thread.
384 video::IImage* generateImage(const std::string &name);
386 video::ITexture* getNormalTexture(const std::string &name);
387 video::SColor getTextureAverageColor(const std::string &name);
388 video::ITexture *getShaderFlagsTexture(bool normamap_present);
392 // The id of the thread that is allowed to use irrlicht directly
393 threadid_t m_main_thread;
394 // The irrlicht device
395 IrrlichtDevice *m_device;
397 // Cache of source images
398 // This should be only accessed from the main thread
399 SourceImageCache m_sourcecache;
401 // Generate a texture
402 u32 generateTexture(const std::string &name);
404 // Generate image based on a string like "stone.png" or "[crack:1:0".
405 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
406 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
408 // Thread-safe cache of what source images are known (true = known)
409 MutexedMap<std::string, bool> m_source_image_existence;
411 // A texture id is index in this array.
412 // The first position contains a NULL texture.
413 std::vector<TextureInfo> m_textureinfo_cache;
414 // Maps a texture name to an index in the former.
415 std::map<std::string, u32> m_name_to_id;
416 // The two former containers are behind this mutex
417 Mutex m_textureinfo_cache_mutex;
419 // Queued texture fetches (to be processed by the main thread)
420 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
422 // Textures that have been overwritten with other ones
423 // but can't be deleted because the ITexture* might still be used
424 std::vector<video::ITexture*> m_texture_trash;
426 // Cached settings needed for making textures from meshes
427 bool m_setting_trilinear_filter;
428 bool m_setting_bilinear_filter;
429 bool m_setting_anisotropic_filter;
432 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
434 return new TextureSource(device);
437 TextureSource::TextureSource(IrrlichtDevice *device):
440 assert(m_device); // Pre-condition
442 m_main_thread = thr_get_current_thread_id();
444 // Add a NULL TextureInfo as the first index, named ""
445 m_textureinfo_cache.push_back(TextureInfo(""));
446 m_name_to_id[""] = 0;
448 // Cache some settings
449 // Note: Since this is only done once, the game must be restarted
450 // for these settings to take effect
451 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
452 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
453 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
456 TextureSource::~TextureSource()
458 video::IVideoDriver* driver = m_device->getVideoDriver();
460 unsigned int textures_before = driver->getTextureCount();
462 for (std::vector<TextureInfo>::iterator iter =
463 m_textureinfo_cache.begin();
464 iter != m_textureinfo_cache.end(); ++iter)
468 driver->removeTexture(iter->texture);
470 m_textureinfo_cache.clear();
472 for (std::vector<video::ITexture*>::iterator iter =
473 m_texture_trash.begin(); iter != m_texture_trash.end();
475 video::ITexture *t = *iter;
477 //cleanup trashed texture
478 driver->removeTexture(t);
481 infostream << "~TextureSource() "<< textures_before << "/"
482 << driver->getTextureCount() << std::endl;
485 u32 TextureSource::getTextureId(const std::string &name)
487 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
491 See if texture already exists
493 MutexAutoLock lock(m_textureinfo_cache_mutex);
494 std::map<std::string, u32>::iterator n;
495 n = m_name_to_id.find(name);
496 if (n != m_name_to_id.end())
505 if (thr_is_current_thread(m_main_thread))
507 return generateTexture(name);
511 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
513 // We're gonna ask the result to be put into here
514 static ResultQueue<std::string, u32, u8, u8> result_queue;
516 // Throw a request in
517 m_get_texture_queue.add(name, 0, 0, &result_queue);
519 /*infostream<<"Waiting for texture from main thread, name=\""
520 <<name<<"\""<<std::endl;*/
525 // Wait result for a second
526 GetResult<std::string, u32, u8, u8>
527 result = result_queue.pop_front(1000);
529 if (result.key == name) {
534 catch(ItemNotFoundException &e)
536 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
541 infostream<<"getTextureId(): Failed"<<std::endl;
546 // Draw an image on top of an another one, using the alpha channel of the
548 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
549 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
551 // Like blit_with_alpha, but only modifies destination pixels that
553 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
554 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
556 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
557 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
558 // color alpha with the destination alpha.
559 // Otherwise, any pixels that are not fully transparent get the color alpha.
560 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
561 video::SColor color, int ratio, bool keep_alpha);
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 (!thr_is_current_thread(m_main_thread)) {
610 errorstream<<"TextureSource::generateTexture() "
611 "called not from main thread"<<std::endl;
615 video::IVideoDriver *driver = m_device->getVideoDriver();
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 void TextureSource::processQueue()
690 //NOTE this is only thread safe for ONE consumer thread!
691 if (!m_get_texture_queue.empty())
693 GetRequest<std::string, u32, u8, u8>
694 request = m_get_texture_queue.pop();
696 /*infostream<<"TextureSource::processQueue(): "
697 <<"got texture request with "
698 <<"name=\""<<request.key<<"\""
701 m_get_texture_queue.pushResult(request, generateTexture(request.key));
705 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
707 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
709 sanity_check(thr_is_current_thread(m_main_thread));
711 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
712 m_source_image_existence.set(name, true);
715 void TextureSource::rebuildImagesAndTextures()
717 MutexAutoLock lock(m_textureinfo_cache_mutex);
719 video::IVideoDriver* driver = m_device->getVideoDriver();
720 sanity_check(driver);
723 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
724 TextureInfo *ti = &m_textureinfo_cache[i];
725 video::IImage *img = generateImage(ti->name);
727 img = Align2Npot2(img, driver);
728 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
729 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
731 // Create texture from resulting image
732 video::ITexture *t = NULL;
734 t = driver->addTexture(ti->name.c_str(), img);
735 guiScalingCache(io::path(ti->name.c_str()), driver, img);
738 video::ITexture *t_old = ti->texture;
743 m_texture_trash.push_back(t_old);
747 video::ITexture* TextureSource::generateTextureFromMesh(
748 const TextureFromMeshParams ¶ms)
750 video::IVideoDriver *driver = m_device->getVideoDriver();
751 sanity_check(driver);
754 const GLubyte* renderstr = glGetString(GL_RENDERER);
755 std::string renderer((char*) renderstr);
757 // use no render to texture hack
759 (renderer.find("Adreno") != std::string::npos) ||
760 (renderer.find("Mali") != std::string::npos) ||
761 (renderer.find("Immersion") != std::string::npos) ||
762 (renderer.find("Tegra") != std::string::npos) ||
763 g_settings->getBool("inventory_image_hack")
765 // Get a scene manager
766 scene::ISceneManager *smgr_main = m_device->getSceneManager();
767 sanity_check(smgr_main);
768 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
771 const float scaling = 0.2;
773 scene::IMeshSceneNode* meshnode =
774 smgr->addMeshSceneNode(params.mesh, NULL,
775 -1, v3f(0,0,0), v3f(0,0,0),
776 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
777 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
778 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
779 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
780 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
781 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
783 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
784 params.camera_position, params.camera_lookat);
785 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
786 camera->setProjectionMatrix(params.camera_projection_matrix, false);
788 smgr->setAmbientLight(params.ambient_light);
789 smgr->addLightSceneNode(0,
790 params.light_position,
792 params.light_radius*scaling);
794 core::dimension2d<u32> screen = driver->getScreenSize();
797 driver->beginScene(true, true, video::SColor(0,0,0,0));
798 driver->clearZBuffer();
801 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
803 irr::video::IImage* rawImage =
804 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
806 u8* pixels = static_cast<u8*>(rawImage->lock());
813 core::rect<s32> source(
814 screen.Width /2 - (screen.Width * (scaling / 2)),
815 screen.Height/2 - (screen.Height * (scaling / 2)),
816 screen.Width /2 + (screen.Width * (scaling / 2)),
817 screen.Height/2 + (screen.Height * (scaling / 2))
820 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
821 partsize.Width, partsize.Height, GL_RGBA,
822 GL_UNSIGNED_BYTE, pixels);
826 // Drop scene manager
829 unsigned int pixelcount = partsize.Width*partsize.Height;
832 for (unsigned int i=0; i < pixelcount; i++) {
850 video::IImage* inventory_image =
851 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
853 rawImage->copyToScaling(inventory_image);
856 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
858 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
859 inventory_image->drop();
862 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
866 driver->makeColorKeyTexture(rtt, v2s32(0,0));
868 if (params.delete_texture_on_shutdown)
869 m_texture_trash.push_back(rtt);
875 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
877 static bool warned = false;
880 errorstream<<"TextureSource::generateTextureFromMesh(): "
881 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
887 // Create render target texture
888 video::ITexture *rtt = driver->addRenderTargetTexture(
889 params.dim, params.rtt_texture_name.c_str(),
890 video::ECF_A8R8G8B8);
893 errorstream<<"TextureSource::generateTextureFromMesh(): "
894 <<"addRenderTargetTexture returned NULL."<<std::endl;
899 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
900 driver->removeTexture(rtt);
901 errorstream<<"TextureSource::generateTextureFromMesh(): "
902 <<"failed to set render target"<<std::endl;
906 // Get a scene manager
907 scene::ISceneManager *smgr_main = m_device->getSceneManager();
909 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
912 scene::IMeshSceneNode* meshnode =
913 smgr->addMeshSceneNode(params.mesh, NULL,
914 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
915 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
916 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
917 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
918 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
919 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
921 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
922 params.camera_position, params.camera_lookat);
923 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
924 camera->setProjectionMatrix(params.camera_projection_matrix, false);
926 smgr->setAmbientLight(params.ambient_light);
927 smgr->addLightSceneNode(0,
928 params.light_position,
930 params.light_radius);
933 driver->beginScene(true, true, video::SColor(0,0,0,0));
937 // Drop scene manager
940 // Unset render target
941 driver->setRenderTarget(0, false, true, 0);
943 if (params.delete_texture_on_shutdown)
944 m_texture_trash.push_back(rtt);
949 video::IImage* TextureSource::generateImage(const std::string &name)
951 // Get the base image
953 const char separator = '^';
954 const char escape = '\\';
955 const char paren_open = '(';
956 const char paren_close = ')';
958 // Find last separator in the name
959 s32 last_separator_pos = -1;
961 for (s32 i = name.size() - 1; i >= 0; i--) {
962 if (i > 0 && name[i-1] == escape)
966 if (paren_bal == 0) {
967 last_separator_pos = i;
968 i = -1; // break out of loop
972 if (paren_bal == 0) {
973 errorstream << "generateImage(): unbalanced parentheses"
974 << "(extranous '(') while generating texture \""
975 << name << "\"" << std::endl;
988 errorstream << "generateImage(): unbalanced parentheses"
989 << "(missing matching '(') while generating texture \""
990 << name << "\"" << std::endl;
995 video::IImage *baseimg = NULL;
998 If separator was found, make the base image
999 using a recursive call.
1001 if (last_separator_pos != -1) {
1002 baseimg = generateImage(name.substr(0, last_separator_pos));
1006 video::IVideoDriver* driver = m_device->getVideoDriver();
1007 sanity_check(driver);
1010 Parse out the last part of the name of the image and act
1014 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1017 If this name is enclosed in parentheses, generate it
1018 and blit it onto the base image
1020 if (last_part_of_name[0] == paren_open
1021 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1022 std::string name2 = last_part_of_name.substr(1,
1023 last_part_of_name.size() - 2);
1024 video::IImage *tmp = generateImage(name2);
1026 errorstream << "generateImage(): "
1027 "Failed to generate \"" << name2 << "\""
1031 core::dimension2d<u32> dim = tmp->getDimension();
1033 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1038 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1039 // Generate image according to part of name
1040 errorstream << "generateImage(): "
1041 "Failed to generate \"" << last_part_of_name << "\""
1045 // If no resulting image, print a warning
1046 if (baseimg == NULL) {
1047 errorstream << "generateImage(): baseimg is NULL (attempted to"
1048 " create texture \"" << name << "\")" << std::endl;
1055 #include <GLES/gl.h>
1057 * Check and align image to npot2 if required by hardware
1058 * @param image image to check for npot2 alignment
1059 * @param driver driver to use for image operations
1060 * @return image or copy of image aligned to npot2
1062 video::IImage * Align2Npot2(video::IImage * image,
1063 video::IVideoDriver* driver)
1065 if (image == NULL) {
1069 core::dimension2d<u32> dim = image->getDimension();
1071 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1072 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1076 unsigned int height = npot2(dim.Height);
1077 unsigned int width = npot2(dim.Width);
1079 if ((dim.Height == height) &&
1080 (dim.Width == width)) {
1084 if (dim.Height > height) {
1088 if (dim.Width > width) {
1092 video::IImage *targetimage =
1093 driver->createImage(video::ECF_A8R8G8B8,
1094 core::dimension2d<u32>(width, height));
1096 if (targetimage != NULL) {
1097 image->copyToScaling(targetimage);
1105 static std::string unescape_string(const std::string &str, const char esc = '\\')
1108 size_t pos = 0, cpos;
1109 out.reserve(str.size());
1111 cpos = str.find_first_of(esc, pos);
1112 if (cpos == std::string::npos) {
1113 out += str.substr(pos);
1116 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1122 bool TextureSource::generateImagePart(std::string part_of_name,
1123 video::IImage *& baseimg)
1125 const char escape = '\\'; // same as in generateImage()
1126 video::IVideoDriver* driver = m_device->getVideoDriver();
1127 sanity_check(driver);
1129 // Stuff starting with [ are special commands
1130 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1132 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1134 image = Align2Npot2(image, driver);
1136 if (image == NULL) {
1137 if (part_of_name != "") {
1138 if (part_of_name.find("_normal.png") == std::string::npos){
1139 errorstream<<"generateImage(): Could not load image \""
1140 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1141 errorstream<<"generateImage(): Creating a dummy"
1142 <<" image for \""<<part_of_name<<"\""<<std::endl;
1144 infostream<<"generateImage(): Could not load normal map \""
1145 <<part_of_name<<"\""<<std::endl;
1146 infostream<<"generateImage(): Creating a dummy"
1147 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1151 // Just create a dummy image
1152 //core::dimension2d<u32> dim(2,2);
1153 core::dimension2d<u32> dim(1,1);
1154 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1155 sanity_check(image != NULL);
1156 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1157 image->setPixel(1,0, video::SColor(255,0,255,0));
1158 image->setPixel(0,1, video::SColor(255,0,0,255));
1159 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1160 image->setPixel(0,0, video::SColor(255,myrand()%256,
1161 myrand()%256,myrand()%256));
1162 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1163 myrand()%256,myrand()%256));
1164 image->setPixel(0,1, video::SColor(255,myrand()%256,
1165 myrand()%256,myrand()%256));
1166 image->setPixel(1,1, video::SColor(255,myrand()%256,
1167 myrand()%256,myrand()%256));*/
1170 // If base image is NULL, load as base.
1171 if (baseimg == NULL)
1173 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1175 Copy it this way to get an alpha channel.
1176 Otherwise images with alpha cannot be blitted on
1177 images that don't have alpha in the original file.
1179 core::dimension2d<u32> dim = image->getDimension();
1180 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1181 image->copyTo(baseimg);
1183 // Else blit on base.
1186 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1187 // Size of the copied area
1188 core::dimension2d<u32> dim = image->getDimension();
1189 //core::dimension2d<u32> dim(16,16);
1190 // Position to copy the blitted to in the base image
1191 core::position2d<s32> pos_to(0,0);
1192 // Position to copy the blitted from in the blitted image
1193 core::position2d<s32> pos_from(0,0);
1195 /*image->copyToWithAlpha(baseimg, pos_to,
1196 core::rect<s32>(pos_from, dim),
1197 video::SColor(255,255,255,255),
1200 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1201 if (dim == dim_dst) {
1202 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1203 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1204 // Upscale overlying image
1205 video::IImage* scaled_image = m_device->getVideoDriver()->
1206 createImage(video::ECF_A8R8G8B8, dim_dst);
1207 image->copyToScaling(scaled_image);
1209 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1210 scaled_image->drop();
1212 // Upscale base image
1213 video::IImage* scaled_base = m_device->getVideoDriver()->
1214 createImage(video::ECF_A8R8G8B8, dim);
1215 baseimg->copyToScaling(scaled_base);
1217 baseimg = scaled_base;
1219 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1227 // A special texture modification
1229 /*infostream<<"generateImage(): generating special "
1230 <<"modification \""<<part_of_name<<"\""
1236 Adds a cracking texture
1237 N = animation frame count, P = crack progression
1239 if (str_starts_with(part_of_name, "[crack"))
1241 if (baseimg == NULL) {
1242 errorstream<<"generateImagePart(): baseimg == NULL "
1243 <<"for part_of_name=\""<<part_of_name
1244 <<"\", cancelling."<<std::endl;
1248 // Crack image number and overlay option
1249 bool use_overlay = (part_of_name[6] == 'o');
1250 Strfnd sf(part_of_name);
1252 s32 frame_count = stoi(sf.next(":"));
1253 s32 progression = stoi(sf.next(":"));
1255 if (progression >= 0) {
1259 It is an image with a number of cracking stages
1262 video::IImage *img_crack = m_sourcecache.getOrLoad(
1263 "crack_anylength.png", m_device);
1266 draw_crack(img_crack, baseimg,
1267 use_overlay, frame_count,
1268 progression, driver);
1274 [combine:WxH:X,Y=filename:X,Y=filename2
1275 Creates a bigger texture from any amount of smaller ones
1277 else if (str_starts_with(part_of_name, "[combine"))
1279 Strfnd sf(part_of_name);
1281 u32 w0 = stoi(sf.next("x"));
1282 u32 h0 = stoi(sf.next(":"));
1283 core::dimension2d<u32> dim(w0,h0);
1284 if (baseimg == NULL) {
1285 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1286 baseimg->fill(video::SColor(0,0,0,0));
1288 while (sf.at_end() == false) {
1289 u32 x = stoi(sf.next(","));
1290 u32 y = stoi(sf.next("="));
1291 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1292 infostream<<"Adding \""<<filename
1293 <<"\" to combined ("<<x<<","<<y<<")"
1295 video::IImage *img = generateImage(filename);
1297 core::dimension2d<u32> dim = img->getDimension();
1298 infostream<<"Size "<<dim.Width
1299 <<"x"<<dim.Height<<std::endl;
1300 core::position2d<s32> pos_base(x, y);
1301 video::IImage *img2 =
1302 driver->createImage(video::ECF_A8R8G8B8, dim);
1305 /*img2->copyToWithAlpha(baseimg, pos_base,
1306 core::rect<s32>(v2s32(0,0), dim),
1307 video::SColor(255,255,255,255),
1309 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1312 errorstream << "generateImagePart(): Failed to load image \""
1313 << filename << "\" for [combine" << std::endl;
1320 else if (str_starts_with(part_of_name, "[brighten"))
1322 if (baseimg == NULL) {
1323 errorstream<<"generateImagePart(): baseimg==NULL "
1324 <<"for part_of_name=\""<<part_of_name
1325 <<"\", cancelling."<<std::endl;
1333 Make image completely opaque.
1334 Used for the leaves texture when in old leaves mode, so
1335 that the transparent parts don't look completely black
1336 when simple alpha channel is used for rendering.
1338 else if (str_starts_with(part_of_name, "[noalpha"))
1340 if (baseimg == NULL){
1341 errorstream<<"generateImagePart(): baseimg==NULL "
1342 <<"for part_of_name=\""<<part_of_name
1343 <<"\", cancelling."<<std::endl;
1347 core::dimension2d<u32> dim = baseimg->getDimension();
1349 // Set alpha to full
1350 for (u32 y=0; y<dim.Height; y++)
1351 for (u32 x=0; x<dim.Width; x++)
1353 video::SColor c = baseimg->getPixel(x,y);
1355 baseimg->setPixel(x,y,c);
1360 Convert one color to transparent.
1362 else if (str_starts_with(part_of_name, "[makealpha:"))
1364 if (baseimg == NULL) {
1365 errorstream<<"generateImagePart(): baseimg == NULL "
1366 <<"for part_of_name=\""<<part_of_name
1367 <<"\", cancelling."<<std::endl;
1371 Strfnd sf(part_of_name.substr(11));
1372 u32 r1 = stoi(sf.next(","));
1373 u32 g1 = stoi(sf.next(","));
1374 u32 b1 = stoi(sf.next(""));
1376 core::dimension2d<u32> dim = baseimg->getDimension();
1378 /*video::IImage *oldbaseimg = baseimg;
1379 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1380 oldbaseimg->copyTo(baseimg);
1381 oldbaseimg->drop();*/
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 u32 g = c.getGreen();
1390 u32 b = c.getBlue();
1391 if (!(r == r1 && g == g1 && b == b1))
1394 baseimg->setPixel(x,y,c);
1399 Rotates and/or flips the image.
1401 N can be a number (between 0 and 7) or a transform name.
1402 Rotations are counter-clockwise.
1404 1 R90 rotate by 90 degrees
1405 2 R180 rotate by 180 degrees
1406 3 R270 rotate by 270 degrees
1408 5 FXR90 flip X then rotate by 90 degrees
1410 7 FYR90 flip Y then rotate by 90 degrees
1412 Note: Transform names can be concatenated to produce
1413 their product (applies the first then the second).
1414 The resulting transform will be equivalent to one of the
1415 eight existing ones, though (see: dihedral group).
1417 else if (str_starts_with(part_of_name, "[transform"))
1419 if (baseimg == NULL) {
1420 errorstream<<"generateImagePart(): baseimg == NULL "
1421 <<"for part_of_name=\""<<part_of_name
1422 <<"\", cancelling."<<std::endl;
1426 u32 transform = parseImageTransform(part_of_name.substr(10));
1427 core::dimension2d<u32> dim = imageTransformDimension(
1428 transform, baseimg->getDimension());
1429 video::IImage *image = driver->createImage(
1430 baseimg->getColorFormat(), dim);
1431 sanity_check(image != NULL);
1432 imageTransform(transform, baseimg, image);
1437 [inventorycube{topimage{leftimage{rightimage
1438 In every subimage, replace ^ with &.
1439 Create an "inventory cube".
1440 NOTE: This should be used only on its own.
1441 Example (a grass block (not actually used in game):
1442 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1444 else if (str_starts_with(part_of_name, "[inventorycube"))
1446 if (baseimg != NULL){
1447 errorstream<<"generateImagePart(): baseimg != NULL "
1448 <<"for part_of_name=\""<<part_of_name
1449 <<"\", cancelling."<<std::endl;
1453 str_replace(part_of_name, '&', '^');
1454 Strfnd sf(part_of_name);
1456 std::string imagename_top = sf.next("{");
1457 std::string imagename_left = sf.next("{");
1458 std::string imagename_right = sf.next("{");
1460 // Generate images for the faces of the cube
1461 video::IImage *img_top = generateImage(imagename_top);
1462 video::IImage *img_left = generateImage(imagename_left);
1463 video::IImage *img_right = generateImage(imagename_right);
1465 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1466 errorstream << "generateImagePart(): Failed to create textures"
1467 << " for inventorycube \"" << part_of_name << "\""
1469 baseimg = generateImage(imagename_top);
1474 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1475 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1477 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1478 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1480 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1481 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1484 // Create textures from images
1485 video::ITexture *texture_top = driver->addTexture(
1486 (imagename_top + "__temp__").c_str(), img_top);
1487 video::ITexture *texture_left = driver->addTexture(
1488 (imagename_left + "__temp__").c_str(), img_left);
1489 video::ITexture *texture_right = driver->addTexture(
1490 (imagename_right + "__temp__").c_str(), img_right);
1491 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1499 Draw a cube mesh into a render target texture
1501 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1502 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1503 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1504 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1505 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1506 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1507 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1508 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1510 TextureFromMeshParams params;
1512 params.dim.set(64, 64);
1513 params.rtt_texture_name = part_of_name + "_RTT";
1514 // We will delete the rtt texture ourselves
1515 params.delete_texture_on_shutdown = false;
1516 params.camera_position.set(0, 1.0, -1.5);
1517 params.camera_position.rotateXZBy(45);
1518 params.camera_lookat.set(0, 0, 0);
1519 // Set orthogonal projection
1520 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1521 1.65, 1.65, 0, 100);
1523 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1524 params.light_position.set(10, 100, -50);
1525 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1526 params.light_radius = 1000;
1528 video::ITexture *rtt = generateTextureFromMesh(params);
1534 driver->removeTexture(texture_top);
1535 driver->removeTexture(texture_left);
1536 driver->removeTexture(texture_right);
1539 baseimg = generateImage(imagename_top);
1543 // Create image of render target
1544 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1545 FATAL_ERROR_IF(!image, "Could not create image of render target");
1548 driver->removeTexture(rtt);
1550 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1553 image->copyTo(baseimg);
1558 [lowpart:percent:filename
1559 Adds the lower part of a texture
1561 else if (str_starts_with(part_of_name, "[lowpart:"))
1563 Strfnd sf(part_of_name);
1565 u32 percent = stoi(sf.next(":"));
1566 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1568 if (baseimg == NULL)
1569 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1570 video::IImage *img = generateImage(filename);
1573 core::dimension2d<u32> dim = img->getDimension();
1574 core::position2d<s32> pos_base(0, 0);
1575 video::IImage *img2 =
1576 driver->createImage(video::ECF_A8R8G8B8, dim);
1579 core::position2d<s32> clippos(0, 0);
1580 clippos.Y = dim.Height * (100-percent) / 100;
1581 core::dimension2d<u32> clipdim = dim;
1582 clipdim.Height = clipdim.Height * percent / 100 + 1;
1583 core::rect<s32> cliprect(clippos, clipdim);
1584 img2->copyToWithAlpha(baseimg, pos_base,
1585 core::rect<s32>(v2s32(0,0), dim),
1586 video::SColor(255,255,255,255),
1593 Crops a frame of a vertical animation.
1594 N = frame count, I = frame index
1596 else if (str_starts_with(part_of_name, "[verticalframe:"))
1598 Strfnd sf(part_of_name);
1600 u32 frame_count = stoi(sf.next(":"));
1601 u32 frame_index = stoi(sf.next(":"));
1603 if (baseimg == NULL){
1604 errorstream<<"generateImagePart(): baseimg != NULL "
1605 <<"for part_of_name=\""<<part_of_name
1606 <<"\", cancelling."<<std::endl;
1610 v2u32 frame_size = baseimg->getDimension();
1611 frame_size.Y /= frame_count;
1613 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1616 errorstream<<"generateImagePart(): Could not create image "
1617 <<"for part_of_name=\""<<part_of_name
1618 <<"\", cancelling."<<std::endl;
1622 // Fill target image with transparency
1623 img->fill(video::SColor(0,0,0,0));
1625 core::dimension2d<u32> dim = frame_size;
1626 core::position2d<s32> pos_dst(0, 0);
1627 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1628 baseimg->copyToWithAlpha(img, pos_dst,
1629 core::rect<s32>(pos_src, dim),
1630 video::SColor(255,255,255,255),
1638 Applies a mask to an image
1640 else if (str_starts_with(part_of_name, "[mask:"))
1642 if (baseimg == NULL) {
1643 errorstream << "generateImage(): baseimg == NULL "
1644 << "for part_of_name=\"" << part_of_name
1645 << "\", cancelling." << std::endl;
1648 Strfnd sf(part_of_name);
1650 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1652 video::IImage *img = generateImage(filename);
1654 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1655 img->getDimension());
1658 errorstream << "generateImage(): Failed to load \""
1659 << filename << "\".";
1664 Overlays image with given color
1665 color = color as ColorString
1667 else if (str_starts_with(part_of_name, "[colorize:"))
1669 Strfnd sf(part_of_name);
1671 std::string color_str = sf.next(":");
1672 std::string ratio_str = sf.next(":");
1674 if (baseimg == NULL) {
1675 errorstream << "generateImagePart(): baseimg != NULL "
1676 << "for part_of_name=\"" << part_of_name
1677 << "\", cancelling." << std::endl;
1681 video::SColor color;
1683 bool keep_alpha = false;
1685 if (!parseColorString(color_str, color, false))
1688 if (is_number(ratio_str))
1689 ratio = mystoi(ratio_str, 0, 255);
1690 else if (ratio_str == "alpha")
1693 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1696 [applyfiltersformesh
1699 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1701 // Apply the "clean transparent" filter, if configured.
1702 if (g_settings->getBool("texture_clean_transparent"))
1703 imageCleanTransparent(baseimg, 127);
1705 /* Upscale textures to user's requested minimum size. This is a trick to make
1706 * filters look as good on low-res textures as on high-res ones, by making
1707 * low-res textures BECOME high-res ones. This is helpful for worlds that
1708 * mix high- and low-res textures, or for mods with least-common-denominator
1709 * textures that don't have the resources to offer high-res alternatives.
1711 s32 scaleto = g_settings->getS32("texture_min_size");
1713 const core::dimension2d<u32> dim = baseimg->getDimension();
1715 /* Calculate scaling needed to make the shortest texture dimension
1716 * equal to the target minimum. If e.g. this is a vertical frames
1717 * animation, the short dimension will be the real size.
1719 u32 xscale = scaleto / dim.Width;
1720 u32 yscale = scaleto / dim.Height;
1721 u32 scale = (xscale > yscale) ? xscale : yscale;
1723 // Never downscale; only scale up by 2x or more.
1725 u32 w = scale * dim.Width;
1726 u32 h = scale * dim.Height;
1727 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1728 video::IImage *newimg = driver->createImage(
1729 baseimg->getColorFormat(), newdim);
1730 baseimg->copyToScaling(newimg);
1738 Resizes the base image to the given dimensions
1740 else if (str_starts_with(part_of_name, "[resize"))
1742 if (baseimg == NULL) {
1743 errorstream << "generateImagePart(): baseimg == NULL "
1744 << "for part_of_name=\""<< part_of_name
1745 << "\", cancelling." << std::endl;
1749 Strfnd sf(part_of_name);
1751 u32 width = stoi(sf.next("x"));
1752 u32 height = stoi(sf.next(""));
1753 core::dimension2d<u32> dim(width, height);
1755 video::IImage* image = m_device->getVideoDriver()->
1756 createImage(video::ECF_A8R8G8B8, dim);
1757 baseimg->copyToScaling(image);
1763 Makes the base image transparent according to the given ratio.
1764 R must be between 0 and 255.
1765 0 means totally transparent.
1766 255 means totally opaque.
1768 else if (str_starts_with(part_of_name, "[opacity:")) {
1769 if (baseimg == NULL) {
1770 errorstream << "generateImagePart(): baseimg == NULL "
1771 << "for part_of_name=\"" << part_of_name
1772 << "\", cancelling." << std::endl;
1776 Strfnd sf(part_of_name);
1779 u32 ratio = mystoi(sf.next(""), 0, 255);
1781 core::dimension2d<u32> dim = baseimg->getDimension();
1783 for (u32 y = 0; y < dim.Height; y++)
1784 for (u32 x = 0; x < dim.Width; x++)
1786 video::SColor c = baseimg->getPixel(x,y);
1787 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1788 baseimg->setPixel(x,y,c);
1793 errorstream << "generateImagePart(): Invalid "
1794 " modification: \"" << part_of_name << "\"" << std::endl;
1802 Draw an image on top of an another one, using the alpha channel of the
1805 This exists because IImage::copyToWithAlpha() doesn't seem to always
1808 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1809 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1811 for (u32 y0=0; y0<size.Y; y0++)
1812 for (u32 x0=0; x0<size.X; x0++)
1814 s32 src_x = src_pos.X + x0;
1815 s32 src_y = src_pos.Y + y0;
1816 s32 dst_x = dst_pos.X + x0;
1817 s32 dst_y = dst_pos.Y + y0;
1818 video::SColor src_c = src->getPixel(src_x, src_y);
1819 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1820 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1821 dst->setPixel(dst_x, dst_y, dst_c);
1826 Draw an image on top of an another one, using the alpha channel of the
1827 source image; only modify fully opaque pixels in destinaion
1829 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1830 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1832 for (u32 y0=0; y0<size.Y; y0++)
1833 for (u32 x0=0; x0<size.X; x0++)
1835 s32 src_x = src_pos.X + x0;
1836 s32 src_y = src_pos.Y + y0;
1837 s32 dst_x = dst_pos.X + x0;
1838 s32 dst_y = dst_pos.Y + y0;
1839 video::SColor src_c = src->getPixel(src_x, src_y);
1840 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1841 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1843 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1844 dst->setPixel(dst_x, dst_y, dst_c);
1849 // This function has been disabled because it is currently unused.
1850 // Feel free to re-enable if you find it handy.
1853 Draw an image on top of an another one, using the specified ratio
1854 modify all partially-opaque pixels in the destination.
1856 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1857 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1859 for (u32 y0 = 0; y0 < size.Y; y0++)
1860 for (u32 x0 = 0; x0 < size.X; x0++)
1862 s32 src_x = src_pos.X + x0;
1863 s32 src_y = src_pos.Y + y0;
1864 s32 dst_x = dst_pos.X + x0;
1865 s32 dst_y = dst_pos.Y + y0;
1866 video::SColor src_c = src->getPixel(src_x, src_y);
1867 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1868 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1871 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1873 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1874 dst->setPixel(dst_x, dst_y, dst_c);
1881 Apply color to destination
1883 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1884 video::SColor color, int ratio, bool keep_alpha)
1886 u32 alpha = color.getAlpha();
1887 video::SColor dst_c;
1888 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1889 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1891 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1892 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1893 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1894 if (dst_alpha > 0) {
1895 dst_c.setAlpha(dst_alpha * alpha / 255);
1896 dst->setPixel(x, y, dst_c);
1899 } else { // replace the color including the alpha
1900 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1901 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1902 if (dst->getPixel(x, y).getAlpha() > 0)
1903 dst->setPixel(x, y, color);
1905 } else { // interpolate between the color and destination
1906 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1907 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1908 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1909 dst_c = dst->getPixel(x, y);
1910 if (dst_c.getAlpha() > 0) {
1911 dst_c = color.getInterpolated(dst_c, interp);
1912 dst->setPixel(x, y, dst_c);
1919 Apply mask to destination
1921 static void apply_mask(video::IImage *mask, video::IImage *dst,
1922 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1924 for (u32 y0 = 0; y0 < size.Y; y0++) {
1925 for (u32 x0 = 0; x0 < size.X; x0++) {
1926 s32 mask_x = x0 + mask_pos.X;
1927 s32 mask_y = y0 + mask_pos.Y;
1928 s32 dst_x = x0 + dst_pos.X;
1929 s32 dst_y = y0 + dst_pos.Y;
1930 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1931 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1932 dst_c.color &= mask_c.color;
1933 dst->setPixel(dst_x, dst_y, dst_c);
1938 static void draw_crack(video::IImage *crack, video::IImage *dst,
1939 bool use_overlay, s32 frame_count, s32 progression,
1940 video::IVideoDriver *driver)
1942 // Dimension of destination image
1943 core::dimension2d<u32> dim_dst = dst->getDimension();
1944 // Dimension of original image
1945 core::dimension2d<u32> dim_crack = crack->getDimension();
1946 // Count of crack stages
1947 s32 crack_count = dim_crack.Height / dim_crack.Width;
1948 // Limit frame_count
1949 if (frame_count > (s32) dim_dst.Height)
1950 frame_count = dim_dst.Height;
1951 if (frame_count < 1)
1953 // Limit progression
1954 if (progression > crack_count-1)
1955 progression = crack_count-1;
1956 // Dimension of a single crack stage
1957 core::dimension2d<u32> dim_crack_cropped(
1961 // Dimension of the scaled crack stage,
1962 // which is the same as the dimension of a single destination frame
1963 core::dimension2d<u32> dim_crack_scaled(
1965 dim_dst.Height / frame_count
1967 // Create cropped and scaled crack images
1968 video::IImage *crack_cropped = driver->createImage(
1969 video::ECF_A8R8G8B8, dim_crack_cropped);
1970 video::IImage *crack_scaled = driver->createImage(
1971 video::ECF_A8R8G8B8, dim_crack_scaled);
1973 if (crack_cropped && crack_scaled)
1976 v2s32 pos_crack(0, progression*dim_crack.Width);
1977 crack->copyTo(crack_cropped,
1979 core::rect<s32>(pos_crack, dim_crack_cropped));
1980 // Scale crack image by copying
1981 crack_cropped->copyToScaling(crack_scaled);
1982 // Copy or overlay crack image onto each frame
1983 for (s32 i = 0; i < frame_count; ++i)
1985 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1988 blit_with_alpha_overlay(crack_scaled, dst,
1989 v2s32(0,0), dst_pos,
1994 blit_with_alpha(crack_scaled, dst,
1995 v2s32(0,0), dst_pos,
2002 crack_scaled->drop();
2005 crack_cropped->drop();
2008 void brighten(video::IImage *image)
2013 core::dimension2d<u32> dim = image->getDimension();
2015 for (u32 y=0; y<dim.Height; y++)
2016 for (u32 x=0; x<dim.Width; x++)
2018 video::SColor c = image->getPixel(x,y);
2019 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2020 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2021 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2022 image->setPixel(x,y,c);
2026 u32 parseImageTransform(const std::string& s)
2028 int total_transform = 0;
2030 std::string transform_names[8];
2031 transform_names[0] = "i";
2032 transform_names[1] = "r90";
2033 transform_names[2] = "r180";
2034 transform_names[3] = "r270";
2035 transform_names[4] = "fx";
2036 transform_names[6] = "fy";
2038 std::size_t pos = 0;
2039 while(pos < s.size())
2042 for (int i = 0; i <= 7; ++i)
2044 const std::string &name_i = transform_names[i];
2046 if (s[pos] == ('0' + i))
2052 else if (!(name_i.empty()) &&
2053 lowercase(s.substr(pos, name_i.size())) == name_i)
2056 pos += name_i.size();
2063 // Multiply total_transform and transform in the group D4
2066 new_total = (transform + total_transform) % 4;
2068 new_total = (transform - total_transform + 8) % 4;
2069 if ((transform >= 4) ^ (total_transform >= 4))
2072 total_transform = new_total;
2074 return total_transform;
2077 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2079 if (transform % 2 == 0)
2082 return core::dimension2d<u32>(dim.Height, dim.Width);
2085 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2087 if (src == NULL || dst == NULL)
2090 core::dimension2d<u32> dstdim = dst->getDimension();
2093 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2094 assert(transform <= 7);
2097 Compute the transformation from source coordinates (sx,sy)
2098 to destination coordinates (dx,dy).
2102 if (transform == 0) // identity
2103 sxn = 0, syn = 2; // sx = dx, sy = dy
2104 else if (transform == 1) // rotate by 90 degrees ccw
2105 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2106 else if (transform == 2) // rotate by 180 degrees
2107 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2108 else if (transform == 3) // rotate by 270 degrees ccw
2109 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2110 else if (transform == 4) // flip x
2111 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2112 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2113 sxn = 2, syn = 0; // sx = dy, sy = dx
2114 else if (transform == 6) // flip y
2115 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2116 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2117 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2119 for (u32 dy=0; dy<dstdim.Height; dy++)
2120 for (u32 dx=0; dx<dstdim.Width; dx++)
2122 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2123 u32 sx = entries[sxn];
2124 u32 sy = entries[syn];
2125 video::SColor c = src->getPixel(sx,sy);
2126 dst->setPixel(dx,dy,c);
2130 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2132 if (isKnownSourceImage("override_normal.png"))
2133 return getTexture("override_normal.png");
2134 std::string fname_base = name;
2135 std::string normal_ext = "_normal.png";
2136 size_t pos = fname_base.find(".");
2137 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2138 if (isKnownSourceImage(fname_normal)) {
2139 // look for image extension and replace it
2141 while ((i = fname_base.find(".", i)) != std::string::npos) {
2142 fname_base.replace(i, 4, normal_ext);
2143 i += normal_ext.length();
2145 return getTexture(fname_base);
2150 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2152 video::IVideoDriver *driver = m_device->getVideoDriver();
2153 video::SColor c(0, 0, 0, 0);
2154 video::ITexture *texture = getTexture(name);
2155 video::IImage *image = driver->createImage(texture,
2156 core::position2d<s32>(0, 0),
2157 texture->getOriginalSize());
2162 core::dimension2d<u32> dim = image->getDimension();
2165 step = dim.Width / 16;
2166 for (u16 x = 0; x < dim.Width; x += step) {
2167 for (u16 y = 0; y < dim.Width; y += step) {
2168 c = image->getPixel(x,y);
2169 if (c.getAlpha() > 0) {
2179 c.setRed(tR / total);
2180 c.setGreen(tG / total);
2181 c.setBlue(tB / total);
2188 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2190 std::string tname = "__shaderFlagsTexture";
2191 tname += normalmap_present ? "1" : "0";
2193 if (isKnownSourceImage(tname)) {
2194 return getTexture(tname);
2196 video::IVideoDriver *driver = m_device->getVideoDriver();
2197 video::IImage *flags_image = driver->createImage(
2198 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2199 sanity_check(flags_image != NULL);
2200 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2201 flags_image->setPixel(0, 0, c);
2202 insertSourceImage(tname, flags_image);
2203 flags_image->drop();
2204 return getTexture(tname);