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"
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 JMutex 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 = 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 JMutexAutoLock 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 (get_current_thread_id() == 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 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
557 // and modifies any destination pixels that are not fully transparent
558 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
559 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
561 // Apply a mask to an image
562 static void apply_mask(video::IImage *mask, video::IImage *dst,
563 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
565 // Draw or overlay a crack
566 static void draw_crack(video::IImage *crack, video::IImage *dst,
567 bool use_overlay, s32 frame_count, s32 progression,
568 video::IVideoDriver *driver);
571 void brighten(video::IImage *image);
572 // Parse a transform name
573 u32 parseImageTransform(const std::string& s);
574 // Apply transform to image dimension
575 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
576 // Apply transform to image data
577 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
580 This method generates all the textures
582 u32 TextureSource::generateTexture(const std::string &name)
584 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
586 // Empty name means texture 0
588 infostream<<"generateTexture(): name is empty"<<std::endl;
594 See if texture already exists
596 JMutexAutoLock lock(m_textureinfo_cache_mutex);
597 std::map<std::string, u32>::iterator n;
598 n = m_name_to_id.find(name);
599 if (n != m_name_to_id.end()) {
605 Calling only allowed from main thread
607 if (get_current_thread_id() != m_main_thread) {
608 errorstream<<"TextureSource::generateTexture() "
609 "called not from main thread"<<std::endl;
613 video::IVideoDriver *driver = m_device->getVideoDriver();
614 sanity_check(driver);
616 video::IImage *img = generateImage(name);
618 video::ITexture *tex = NULL;
622 img = Align2Npot2(img, driver);
624 // Create texture from resulting image
625 tex = driver->addTexture(name.c_str(), img);
626 guiScalingCache(io::path(name.c_str()), driver, img);
631 Add texture to caches (add NULL textures too)
634 JMutexAutoLock lock(m_textureinfo_cache_mutex);
636 u32 id = m_textureinfo_cache.size();
637 TextureInfo ti(name, tex);
638 m_textureinfo_cache.push_back(ti);
639 m_name_to_id[name] = id;
644 std::string TextureSource::getTextureName(u32 id)
646 JMutexAutoLock lock(m_textureinfo_cache_mutex);
648 if (id >= m_textureinfo_cache.size())
650 errorstream<<"TextureSource::getTextureName(): id="<<id
651 <<" >= m_textureinfo_cache.size()="
652 <<m_textureinfo_cache.size()<<std::endl;
656 return m_textureinfo_cache[id].name;
659 video::ITexture* TextureSource::getTexture(u32 id)
661 JMutexAutoLock lock(m_textureinfo_cache_mutex);
663 if (id >= m_textureinfo_cache.size())
666 return m_textureinfo_cache[id].texture;
669 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
671 u32 actual_id = getTextureId(name);
675 return getTexture(actual_id);
678 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
680 return getTexture(name + "^[applyfiltersformesh", id);
683 void TextureSource::processQueue()
688 //NOTE this is only thread safe for ONE consumer thread!
689 if (!m_get_texture_queue.empty())
691 GetRequest<std::string, u32, u8, u8>
692 request = m_get_texture_queue.pop();
694 /*infostream<<"TextureSource::processQueue(): "
695 <<"got texture request with "
696 <<"name=\""<<request.key<<"\""
699 m_get_texture_queue.pushResult(request, generateTexture(request.key));
703 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
705 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
707 sanity_check(get_current_thread_id() == m_main_thread);
709 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
710 m_source_image_existence.set(name, true);
713 void TextureSource::rebuildImagesAndTextures()
715 JMutexAutoLock lock(m_textureinfo_cache_mutex);
717 video::IVideoDriver* driver = m_device->getVideoDriver();
718 sanity_check(driver);
721 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
722 TextureInfo *ti = &m_textureinfo_cache[i];
723 video::IImage *img = generateImage(ti->name);
725 img = Align2Npot2(img, driver);
726 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
727 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
729 // Create texture from resulting image
730 video::ITexture *t = NULL;
732 t = driver->addTexture(ti->name.c_str(), img);
733 guiScalingCache(io::path(ti->name.c_str()), driver, img);
736 video::ITexture *t_old = ti->texture;
741 m_texture_trash.push_back(t_old);
745 video::ITexture* TextureSource::generateTextureFromMesh(
746 const TextureFromMeshParams ¶ms)
748 video::IVideoDriver *driver = m_device->getVideoDriver();
749 sanity_check(driver);
752 const GLubyte* renderstr = glGetString(GL_RENDERER);
753 std::string renderer((char*) renderstr);
755 // use no render to texture hack
757 (renderer.find("Adreno") != std::string::npos) ||
758 (renderer.find("Mali") != std::string::npos) ||
759 (renderer.find("Immersion") != std::string::npos) ||
760 (renderer.find("Tegra") != std::string::npos) ||
761 g_settings->getBool("inventory_image_hack")
763 // Get a scene manager
764 scene::ISceneManager *smgr_main = m_device->getSceneManager();
765 sanity_check(smgr_main);
766 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
769 const float scaling = 0.2;
771 scene::IMeshSceneNode* meshnode =
772 smgr->addMeshSceneNode(params.mesh, NULL,
773 -1, v3f(0,0,0), v3f(0,0,0),
774 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
775 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
776 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
777 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
778 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
779 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
781 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
782 params.camera_position, params.camera_lookat);
783 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
784 camera->setProjectionMatrix(params.camera_projection_matrix, false);
786 smgr->setAmbientLight(params.ambient_light);
787 smgr->addLightSceneNode(0,
788 params.light_position,
790 params.light_radius*scaling);
792 core::dimension2d<u32> screen = driver->getScreenSize();
795 driver->beginScene(true, true, video::SColor(0,0,0,0));
796 driver->clearZBuffer();
799 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
801 irr::video::IImage* rawImage =
802 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
804 u8* pixels = static_cast<u8*>(rawImage->lock());
811 core::rect<s32> source(
812 screen.Width /2 - (screen.Width * (scaling / 2)),
813 screen.Height/2 - (screen.Height * (scaling / 2)),
814 screen.Width /2 + (screen.Width * (scaling / 2)),
815 screen.Height/2 + (screen.Height * (scaling / 2))
818 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
819 partsize.Width, partsize.Height, GL_RGBA,
820 GL_UNSIGNED_BYTE, pixels);
824 // Drop scene manager
827 unsigned int pixelcount = partsize.Width*partsize.Height;
830 for (unsigned int i=0; i < pixelcount; i++) {
848 video::IImage* inventory_image =
849 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
851 rawImage->copyToScaling(inventory_image);
854 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
856 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
857 inventory_image->drop();
860 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
864 driver->makeColorKeyTexture(rtt, v2s32(0,0));
866 if (params.delete_texture_on_shutdown)
867 m_texture_trash.push_back(rtt);
873 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
875 static bool warned = false;
878 errorstream<<"TextureSource::generateTextureFromMesh(): "
879 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
885 // Create render target texture
886 video::ITexture *rtt = driver->addRenderTargetTexture(
887 params.dim, params.rtt_texture_name.c_str(),
888 video::ECF_A8R8G8B8);
891 errorstream<<"TextureSource::generateTextureFromMesh(): "
892 <<"addRenderTargetTexture returned NULL."<<std::endl;
897 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
898 driver->removeTexture(rtt);
899 errorstream<<"TextureSource::generateTextureFromMesh(): "
900 <<"failed to set render target"<<std::endl;
904 // Get a scene manager
905 scene::ISceneManager *smgr_main = m_device->getSceneManager();
907 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
910 scene::IMeshSceneNode* meshnode =
911 smgr->addMeshSceneNode(params.mesh, NULL,
912 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
913 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
914 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
915 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
916 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
917 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
919 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
920 params.camera_position, params.camera_lookat);
921 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
922 camera->setProjectionMatrix(params.camera_projection_matrix, false);
924 smgr->setAmbientLight(params.ambient_light);
925 smgr->addLightSceneNode(0,
926 params.light_position,
928 params.light_radius);
931 driver->beginScene(true, true, video::SColor(0,0,0,0));
935 // Drop scene manager
938 // Unset render target
939 driver->setRenderTarget(0, false, true, 0);
941 if (params.delete_texture_on_shutdown)
942 m_texture_trash.push_back(rtt);
947 video::IImage* TextureSource::generateImage(const std::string &name)
953 const char separator = '^';
954 const char paren_open = '(';
955 const char paren_close = ')';
957 // Find last separator in the name
958 s32 last_separator_pos = -1;
960 for (s32 i = name.size() - 1; i >= 0; i--) {
963 if (paren_bal == 0) {
964 last_separator_pos = i;
965 i = -1; // break out of loop
969 if (paren_bal == 0) {
970 errorstream << "generateImage(): unbalanced parentheses"
971 << "(extranous '(') while generating texture \""
972 << name << "\"" << std::endl;
985 errorstream << "generateImage(): unbalanced parentheses"
986 << "(missing matching '(') while generating texture \""
987 << name << "\"" << std::endl;
992 video::IImage *baseimg = NULL;
995 If separator was found, make the base image
996 using a recursive call.
998 if (last_separator_pos != -1) {
999 baseimg = generateImage(name.substr(0, last_separator_pos));
1003 video::IVideoDriver* driver = m_device->getVideoDriver();
1004 sanity_check(driver);
1007 Parse out the last part of the name of the image and act
1011 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1014 If this name is enclosed in parentheses, generate it
1015 and blit it onto the base image
1017 if (last_part_of_name[0] == paren_open
1018 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1019 std::string name2 = last_part_of_name.substr(1,
1020 last_part_of_name.size() - 2);
1021 video::IImage *tmp = generateImage(name2);
1023 errorstream << "generateImage(): "
1024 "Failed to generate \"" << name2 << "\""
1028 core::dimension2d<u32> dim = tmp->getDimension();
1030 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1031 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1033 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1034 // Generate image according to part of name
1035 errorstream << "generateImage(): "
1036 "Failed to generate \"" << last_part_of_name << "\""
1040 // If no resulting image, print a warning
1041 if (baseimg == NULL) {
1042 errorstream << "generateImage(): baseimg is NULL (attempted to"
1043 " create texture \"" << name << "\")" << std::endl;
1050 #include <GLES/gl.h>
1052 * Check and align image to npot2 if required by hardware
1053 * @param image image to check for npot2 alignment
1054 * @param driver driver to use for image operations
1055 * @return image or copy of image aligned to npot2
1057 video::IImage * Align2Npot2(video::IImage * image,
1058 video::IVideoDriver* driver)
1060 if (image == NULL) {
1064 core::dimension2d<u32> dim = image->getDimension();
1066 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1067 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1071 unsigned int height = npot2(dim.Height);
1072 unsigned int width = npot2(dim.Width);
1074 if ((dim.Height == height) &&
1075 (dim.Width == width)) {
1079 if (dim.Height > height) {
1083 if (dim.Width > width) {
1087 video::IImage *targetimage =
1088 driver->createImage(video::ECF_A8R8G8B8,
1089 core::dimension2d<u32>(width, height));
1091 if (targetimage != NULL) {
1092 image->copyToScaling(targetimage);
1100 bool TextureSource::generateImagePart(std::string part_of_name,
1101 video::IImage *& baseimg)
1103 video::IVideoDriver* driver = m_device->getVideoDriver();
1104 sanity_check(driver);
1106 // Stuff starting with [ are special commands
1107 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1109 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1111 image = Align2Npot2(image, driver);
1113 if (image == NULL) {
1114 if (part_of_name != "") {
1115 if (part_of_name.find("_normal.png") == std::string::npos){
1116 errorstream<<"generateImage(): Could not load image \""
1117 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1118 errorstream<<"generateImage(): Creating a dummy"
1119 <<" image for \""<<part_of_name<<"\""<<std::endl;
1121 infostream<<"generateImage(): Could not load normal map \""
1122 <<part_of_name<<"\""<<std::endl;
1123 infostream<<"generateImage(): Creating a dummy"
1124 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1128 // Just create a dummy image
1129 //core::dimension2d<u32> dim(2,2);
1130 core::dimension2d<u32> dim(1,1);
1131 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1132 sanity_check(image != NULL);
1133 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1134 image->setPixel(1,0, video::SColor(255,0,255,0));
1135 image->setPixel(0,1, video::SColor(255,0,0,255));
1136 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1137 image->setPixel(0,0, video::SColor(255,myrand()%256,
1138 myrand()%256,myrand()%256));
1139 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1140 myrand()%256,myrand()%256));
1141 image->setPixel(0,1, video::SColor(255,myrand()%256,
1142 myrand()%256,myrand()%256));
1143 image->setPixel(1,1, video::SColor(255,myrand()%256,
1144 myrand()%256,myrand()%256));*/
1147 // If base image is NULL, load as base.
1148 if (baseimg == NULL)
1150 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1152 Copy it this way to get an alpha channel.
1153 Otherwise images with alpha cannot be blitted on
1154 images that don't have alpha in the original file.
1156 core::dimension2d<u32> dim = image->getDimension();
1157 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1158 image->copyTo(baseimg);
1160 // Else blit on base.
1163 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1164 // Size of the copied area
1165 core::dimension2d<u32> dim = image->getDimension();
1166 //core::dimension2d<u32> dim(16,16);
1167 // Position to copy the blitted to in the base image
1168 core::position2d<s32> pos_to(0,0);
1169 // Position to copy the blitted from in the blitted image
1170 core::position2d<s32> pos_from(0,0);
1172 /*image->copyToWithAlpha(baseimg, pos_to,
1173 core::rect<s32>(pos_from, dim),
1174 video::SColor(255,255,255,255),
1176 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1183 // A special texture modification
1185 /*infostream<<"generateImage(): generating special "
1186 <<"modification \""<<part_of_name<<"\""
1192 Adds a cracking texture
1193 N = animation frame count, P = crack progression
1195 if (str_starts_with(part_of_name, "[crack"))
1197 if (baseimg == NULL) {
1198 errorstream<<"generateImagePart(): baseimg == NULL "
1199 <<"for part_of_name=\""<<part_of_name
1200 <<"\", cancelling."<<std::endl;
1204 // Crack image number and overlay option
1205 bool use_overlay = (part_of_name[6] == 'o');
1206 Strfnd sf(part_of_name);
1208 s32 frame_count = stoi(sf.next(":"));
1209 s32 progression = stoi(sf.next(":"));
1211 if (progression >= 0) {
1215 It is an image with a number of cracking stages
1218 video::IImage *img_crack = m_sourcecache.getOrLoad(
1219 "crack_anylength.png", m_device);
1222 draw_crack(img_crack, baseimg,
1223 use_overlay, frame_count,
1224 progression, driver);
1230 [combine:WxH:X,Y=filename:X,Y=filename2
1231 Creates a bigger texture from an amount of smaller ones
1233 else if (str_starts_with(part_of_name, "[combine"))
1235 Strfnd sf(part_of_name);
1237 u32 w0 = stoi(sf.next("x"));
1238 u32 h0 = stoi(sf.next(":"));
1239 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1240 core::dimension2d<u32> dim(w0,h0);
1241 if (baseimg == NULL) {
1242 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1243 baseimg->fill(video::SColor(0,0,0,0));
1245 while (sf.atend() == false) {
1246 u32 x = stoi(sf.next(","));
1247 u32 y = stoi(sf.next("="));
1248 std::string filename = sf.next(":");
1249 infostream<<"Adding \""<<filename
1250 <<"\" to combined ("<<x<<","<<y<<")"
1252 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1254 core::dimension2d<u32> dim = img->getDimension();
1255 infostream<<"Size "<<dim.Width
1256 <<"x"<<dim.Height<<std::endl;
1257 core::position2d<s32> pos_base(x, y);
1258 video::IImage *img2 =
1259 driver->createImage(video::ECF_A8R8G8B8, dim);
1262 /*img2->copyToWithAlpha(baseimg, pos_base,
1263 core::rect<s32>(v2s32(0,0), dim),
1264 video::SColor(255,255,255,255),
1266 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1269 errorstream << "generateImagePart(): Failed to load image \""
1270 << filename << "\" for [combine" << std::endl;
1277 else if (str_starts_with(part_of_name, "[brighten"))
1279 if (baseimg == NULL) {
1280 errorstream<<"generateImagePart(): baseimg==NULL "
1281 <<"for part_of_name=\""<<part_of_name
1282 <<"\", cancelling."<<std::endl;
1290 Make image completely opaque.
1291 Used for the leaves texture when in old leaves mode, so
1292 that the transparent parts don't look completely black
1293 when simple alpha channel is used for rendering.
1295 else if (str_starts_with(part_of_name, "[noalpha"))
1297 if (baseimg == NULL){
1298 errorstream<<"generateImagePart(): baseimg==NULL "
1299 <<"for part_of_name=\""<<part_of_name
1300 <<"\", cancelling."<<std::endl;
1304 core::dimension2d<u32> dim = baseimg->getDimension();
1306 // Set alpha to full
1307 for (u32 y=0; y<dim.Height; y++)
1308 for (u32 x=0; x<dim.Width; x++)
1310 video::SColor c = baseimg->getPixel(x,y);
1312 baseimg->setPixel(x,y,c);
1317 Convert one color to transparent.
1319 else if (str_starts_with(part_of_name, "[makealpha:"))
1321 if (baseimg == NULL) {
1322 errorstream<<"generateImagePart(): baseimg == NULL "
1323 <<"for part_of_name=\""<<part_of_name
1324 <<"\", cancelling."<<std::endl;
1328 Strfnd sf(part_of_name.substr(11));
1329 u32 r1 = stoi(sf.next(","));
1330 u32 g1 = stoi(sf.next(","));
1331 u32 b1 = stoi(sf.next(""));
1332 std::string filename = sf.next("");
1334 core::dimension2d<u32> dim = baseimg->getDimension();
1336 /*video::IImage *oldbaseimg = baseimg;
1337 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1338 oldbaseimg->copyTo(baseimg);
1339 oldbaseimg->drop();*/
1341 // Set alpha to full
1342 for (u32 y=0; y<dim.Height; y++)
1343 for (u32 x=0; x<dim.Width; x++)
1345 video::SColor c = baseimg->getPixel(x,y);
1347 u32 g = c.getGreen();
1348 u32 b = c.getBlue();
1349 if (!(r == r1 && g == g1 && b == b1))
1352 baseimg->setPixel(x,y,c);
1357 Rotates and/or flips the image.
1359 N can be a number (between 0 and 7) or a transform name.
1360 Rotations are counter-clockwise.
1362 1 R90 rotate by 90 degrees
1363 2 R180 rotate by 180 degrees
1364 3 R270 rotate by 270 degrees
1366 5 FXR90 flip X then rotate by 90 degrees
1368 7 FYR90 flip Y then rotate by 90 degrees
1370 Note: Transform names can be concatenated to produce
1371 their product (applies the first then the second).
1372 The resulting transform will be equivalent to one of the
1373 eight existing ones, though (see: dihedral group).
1375 else if (str_starts_with(part_of_name, "[transform"))
1377 if (baseimg == NULL) {
1378 errorstream<<"generateImagePart(): baseimg == NULL "
1379 <<"for part_of_name=\""<<part_of_name
1380 <<"\", cancelling."<<std::endl;
1384 u32 transform = parseImageTransform(part_of_name.substr(10));
1385 core::dimension2d<u32> dim = imageTransformDimension(
1386 transform, baseimg->getDimension());
1387 video::IImage *image = driver->createImage(
1388 baseimg->getColorFormat(), dim);
1389 sanity_check(image != NULL);
1390 imageTransform(transform, baseimg, image);
1395 [inventorycube{topimage{leftimage{rightimage
1396 In every subimage, replace ^ with &.
1397 Create an "inventory cube".
1398 NOTE: This should be used only on its own.
1399 Example (a grass block (not actually used in game):
1400 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1402 else if (str_starts_with(part_of_name, "[inventorycube"))
1404 if (baseimg != NULL){
1405 errorstream<<"generateImagePart(): baseimg != NULL "
1406 <<"for part_of_name=\""<<part_of_name
1407 <<"\", cancelling."<<std::endl;
1411 str_replace(part_of_name, '&', '^');
1412 Strfnd sf(part_of_name);
1414 std::string imagename_top = sf.next("{");
1415 std::string imagename_left = sf.next("{");
1416 std::string imagename_right = sf.next("{");
1418 // Generate images for the faces of the cube
1419 video::IImage *img_top = generateImage(imagename_top);
1420 video::IImage *img_left = generateImage(imagename_left);
1421 video::IImage *img_right = generateImage(imagename_right);
1423 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1424 errorstream << "generateImagePart(): Failed to create textures"
1425 << " for inventorycube \"" << part_of_name << "\""
1427 baseimg = generateImage(imagename_top);
1432 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1433 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1435 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1436 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1438 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1439 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1442 // Create textures from images
1443 video::ITexture *texture_top = driver->addTexture(
1444 (imagename_top + "__temp__").c_str(), img_top);
1445 video::ITexture *texture_left = driver->addTexture(
1446 (imagename_left + "__temp__").c_str(), img_left);
1447 video::ITexture *texture_right = driver->addTexture(
1448 (imagename_right + "__temp__").c_str(), img_right);
1449 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1457 Draw a cube mesh into a render target texture
1459 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1460 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1461 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1462 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1463 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1464 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1465 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1466 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1468 TextureFromMeshParams params;
1470 params.dim.set(64, 64);
1471 params.rtt_texture_name = part_of_name + "_RTT";
1472 // We will delete the rtt texture ourselves
1473 params.delete_texture_on_shutdown = false;
1474 params.camera_position.set(0, 1.0, -1.5);
1475 params.camera_position.rotateXZBy(45);
1476 params.camera_lookat.set(0, 0, 0);
1477 // Set orthogonal projection
1478 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1479 1.65, 1.65, 0, 100);
1481 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1482 params.light_position.set(10, 100, -50);
1483 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1484 params.light_radius = 1000;
1486 video::ITexture *rtt = generateTextureFromMesh(params);
1492 driver->removeTexture(texture_top);
1493 driver->removeTexture(texture_left);
1494 driver->removeTexture(texture_right);
1497 baseimg = generateImage(imagename_top);
1501 // Create image of render target
1502 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1503 FATAL_ERROR_IF(!image, "Could not create image of render target");
1506 driver->removeTexture(rtt);
1508 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1511 image->copyTo(baseimg);
1516 [lowpart:percent:filename
1517 Adds the lower part of a texture
1519 else if (str_starts_with(part_of_name, "[lowpart:"))
1521 Strfnd sf(part_of_name);
1523 u32 percent = stoi(sf.next(":"));
1524 std::string filename = sf.next(":");
1525 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1527 if (baseimg == NULL)
1528 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1529 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1532 core::dimension2d<u32> dim = img->getDimension();
1533 core::position2d<s32> pos_base(0, 0);
1534 video::IImage *img2 =
1535 driver->createImage(video::ECF_A8R8G8B8, dim);
1538 core::position2d<s32> clippos(0, 0);
1539 clippos.Y = dim.Height * (100-percent) / 100;
1540 core::dimension2d<u32> clipdim = dim;
1541 clipdim.Height = clipdim.Height * percent / 100 + 1;
1542 core::rect<s32> cliprect(clippos, clipdim);
1543 img2->copyToWithAlpha(baseimg, pos_base,
1544 core::rect<s32>(v2s32(0,0), dim),
1545 video::SColor(255,255,255,255),
1552 Crops a frame of a vertical animation.
1553 N = frame count, I = frame index
1555 else if (str_starts_with(part_of_name, "[verticalframe:"))
1557 Strfnd sf(part_of_name);
1559 u32 frame_count = stoi(sf.next(":"));
1560 u32 frame_index = stoi(sf.next(":"));
1562 if (baseimg == NULL){
1563 errorstream<<"generateImagePart(): baseimg != NULL "
1564 <<"for part_of_name=\""<<part_of_name
1565 <<"\", cancelling."<<std::endl;
1569 v2u32 frame_size = baseimg->getDimension();
1570 frame_size.Y /= frame_count;
1572 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1575 errorstream<<"generateImagePart(): Could not create image "
1576 <<"for part_of_name=\""<<part_of_name
1577 <<"\", cancelling."<<std::endl;
1581 // Fill target image with transparency
1582 img->fill(video::SColor(0,0,0,0));
1584 core::dimension2d<u32> dim = frame_size;
1585 core::position2d<s32> pos_dst(0, 0);
1586 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1587 baseimg->copyToWithAlpha(img, pos_dst,
1588 core::rect<s32>(pos_src, dim),
1589 video::SColor(255,255,255,255),
1597 Applies a mask to an image
1599 else if (str_starts_with(part_of_name, "[mask:"))
1601 if (baseimg == NULL) {
1602 errorstream << "generateImage(): baseimg == NULL "
1603 << "for part_of_name=\"" << part_of_name
1604 << "\", cancelling." << std::endl;
1607 Strfnd sf(part_of_name);
1609 std::string filename = sf.next(":");
1611 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1613 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1614 img->getDimension());
1617 errorstream << "generateImage(): Failed to load \""
1618 << filename << "\".";
1623 Overlays image with given color
1624 color = color as ColorString
1626 else if (str_starts_with(part_of_name, "[colorize:"))
1628 Strfnd sf(part_of_name);
1630 std::string color_str = sf.next(":");
1631 std::string ratio_str = sf.next(":");
1633 if (baseimg == NULL) {
1634 errorstream << "generateImagePart(): baseimg != NULL "
1635 << "for part_of_name=\"" << part_of_name
1636 << "\", cancelling." << std::endl;
1640 video::SColor color;
1643 if (!parseColorString(color_str, color, false))
1646 if (is_number(ratio_str))
1647 ratio = mystoi(ratio_str, 0, 255);
1649 core::dimension2d<u32> dim = baseimg->getDimension();
1650 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1653 errorstream << "generateImagePart(): Could not create image "
1654 << "for part_of_name=\"" << part_of_name
1655 << "\", cancelling." << std::endl;
1659 img->fill(video::SColor(color));
1660 // Overlay the colored image
1661 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1664 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1666 // Apply the "clean transparent" filter, if configured.
1667 if (g_settings->getBool("texture_clean_transparent"))
1668 imageCleanTransparent(baseimg, 127);
1670 /* Upscale textures to user's requested minimum size. This is a trick to make
1671 * filters look as good on low-res textures as on high-res ones, by making
1672 * low-res textures BECOME high-res ones. This is helpful for worlds that
1673 * mix high- and low-res textures, or for mods with least-common-denominator
1674 * textures that don't have the resources to offer high-res alternatives.
1676 s32 scaleto = g_settings->getS32("texture_min_size");
1678 const core::dimension2d<u32> dim = baseimg->getDimension();
1680 /* Calculate scaling needed to make the shortest texture dimension
1681 * equal to the target minimum. If e.g. this is a vertical frames
1682 * animation, the short dimension will be the real size.
1684 u32 xscale = scaleto / dim.Width;
1685 u32 yscale = scaleto / dim.Height;
1686 u32 scale = (xscale > yscale) ? xscale : yscale;
1688 // Never downscale; only scale up by 2x or more.
1690 u32 w = scale * dim.Width;
1691 u32 h = scale * dim.Height;
1692 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1693 video::IImage *newimg = driver->createImage(
1694 baseimg->getColorFormat(), newdim);
1695 baseimg->copyToScaling(newimg);
1703 errorstream << "generateImagePart(): Invalid "
1704 " modification: \"" << part_of_name << "\"" << std::endl;
1712 Draw an image on top of an another one, using the alpha channel of the
1715 This exists because IImage::copyToWithAlpha() doesn't seem to always
1718 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1719 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1721 for (u32 y0=0; y0<size.Y; y0++)
1722 for (u32 x0=0; x0<size.X; x0++)
1724 s32 src_x = src_pos.X + x0;
1725 s32 src_y = src_pos.Y + y0;
1726 s32 dst_x = dst_pos.X + x0;
1727 s32 dst_y = dst_pos.Y + y0;
1728 video::SColor src_c = src->getPixel(src_x, src_y);
1729 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1730 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1731 dst->setPixel(dst_x, dst_y, dst_c);
1736 Draw an image on top of an another one, using the alpha channel of the
1737 source image; only modify fully opaque pixels in destinaion
1739 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1740 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1742 for (u32 y0=0; y0<size.Y; y0++)
1743 for (u32 x0=0; x0<size.X; x0++)
1745 s32 src_x = src_pos.X + x0;
1746 s32 src_y = src_pos.Y + y0;
1747 s32 dst_x = dst_pos.X + x0;
1748 s32 dst_y = dst_pos.Y + y0;
1749 video::SColor src_c = src->getPixel(src_x, src_y);
1750 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1751 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1753 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1754 dst->setPixel(dst_x, dst_y, dst_c);
1760 Draw an image on top of an another one, using the specified ratio
1761 modify all partially-opaque pixels in the destination.
1763 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1764 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1766 for (u32 y0 = 0; y0 < size.Y; y0++)
1767 for (u32 x0 = 0; x0 < size.X; x0++)
1769 s32 src_x = src_pos.X + x0;
1770 s32 src_y = src_pos.Y + y0;
1771 s32 dst_x = dst_pos.X + x0;
1772 s32 dst_y = dst_pos.Y + y0;
1773 video::SColor src_c = src->getPixel(src_x, src_y);
1774 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1775 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1778 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1780 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1781 dst->setPixel(dst_x, dst_y, dst_c);
1787 Apply mask to destination
1789 static void apply_mask(video::IImage *mask, video::IImage *dst,
1790 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1792 for (u32 y0 = 0; y0 < size.Y; y0++) {
1793 for (u32 x0 = 0; x0 < size.X; x0++) {
1794 s32 mask_x = x0 + mask_pos.X;
1795 s32 mask_y = y0 + mask_pos.Y;
1796 s32 dst_x = x0 + dst_pos.X;
1797 s32 dst_y = y0 + dst_pos.Y;
1798 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1799 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1800 dst_c.color &= mask_c.color;
1801 dst->setPixel(dst_x, dst_y, dst_c);
1806 static void draw_crack(video::IImage *crack, video::IImage *dst,
1807 bool use_overlay, s32 frame_count, s32 progression,
1808 video::IVideoDriver *driver)
1810 // Dimension of destination image
1811 core::dimension2d<u32> dim_dst = dst->getDimension();
1812 // Dimension of original image
1813 core::dimension2d<u32> dim_crack = crack->getDimension();
1814 // Count of crack stages
1815 s32 crack_count = dim_crack.Height / dim_crack.Width;
1816 // Limit frame_count
1817 if (frame_count > (s32) dim_dst.Height)
1818 frame_count = dim_dst.Height;
1819 if (frame_count < 1)
1821 // Limit progression
1822 if (progression > crack_count-1)
1823 progression = crack_count-1;
1824 // Dimension of a single crack stage
1825 core::dimension2d<u32> dim_crack_cropped(
1829 // Dimension of the scaled crack stage,
1830 // which is the same as the dimension of a single destination frame
1831 core::dimension2d<u32> dim_crack_scaled(
1833 dim_dst.Height / frame_count
1835 // Create cropped and scaled crack images
1836 video::IImage *crack_cropped = driver->createImage(
1837 video::ECF_A8R8G8B8, dim_crack_cropped);
1838 video::IImage *crack_scaled = driver->createImage(
1839 video::ECF_A8R8G8B8, dim_crack_scaled);
1841 if (crack_cropped && crack_scaled)
1844 v2s32 pos_crack(0, progression*dim_crack.Width);
1845 crack->copyTo(crack_cropped,
1847 core::rect<s32>(pos_crack, dim_crack_cropped));
1848 // Scale crack image by copying
1849 crack_cropped->copyToScaling(crack_scaled);
1850 // Copy or overlay crack image onto each frame
1851 for (s32 i = 0; i < frame_count; ++i)
1853 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1856 blit_with_alpha_overlay(crack_scaled, dst,
1857 v2s32(0,0), dst_pos,
1862 blit_with_alpha(crack_scaled, dst,
1863 v2s32(0,0), dst_pos,
1870 crack_scaled->drop();
1873 crack_cropped->drop();
1876 void brighten(video::IImage *image)
1881 core::dimension2d<u32> dim = image->getDimension();
1883 for (u32 y=0; y<dim.Height; y++)
1884 for (u32 x=0; x<dim.Width; x++)
1886 video::SColor c = image->getPixel(x,y);
1887 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1888 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1889 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1890 image->setPixel(x,y,c);
1894 u32 parseImageTransform(const std::string& s)
1896 int total_transform = 0;
1898 std::string transform_names[8];
1899 transform_names[0] = "i";
1900 transform_names[1] = "r90";
1901 transform_names[2] = "r180";
1902 transform_names[3] = "r270";
1903 transform_names[4] = "fx";
1904 transform_names[6] = "fy";
1906 std::size_t pos = 0;
1907 while(pos < s.size())
1910 for (int i = 0; i <= 7; ++i)
1912 const std::string &name_i = transform_names[i];
1914 if (s[pos] == ('0' + i))
1920 else if (!(name_i.empty()) &&
1921 lowercase(s.substr(pos, name_i.size())) == name_i)
1924 pos += name_i.size();
1931 // Multiply total_transform and transform in the group D4
1934 new_total = (transform + total_transform) % 4;
1936 new_total = (transform - total_transform + 8) % 4;
1937 if ((transform >= 4) ^ (total_transform >= 4))
1940 total_transform = new_total;
1942 return total_transform;
1945 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1947 if (transform % 2 == 0)
1950 return core::dimension2d<u32>(dim.Height, dim.Width);
1953 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1955 if (src == NULL || dst == NULL)
1958 core::dimension2d<u32> dstdim = dst->getDimension();
1961 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1962 assert(transform <= 7);
1965 Compute the transformation from source coordinates (sx,sy)
1966 to destination coordinates (dx,dy).
1970 if (transform == 0) // identity
1971 sxn = 0, syn = 2; // sx = dx, sy = dy
1972 else if (transform == 1) // rotate by 90 degrees ccw
1973 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1974 else if (transform == 2) // rotate by 180 degrees
1975 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1976 else if (transform == 3) // rotate by 270 degrees ccw
1977 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1978 else if (transform == 4) // flip x
1979 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1980 else if (transform == 5) // flip x then rotate by 90 degrees ccw
1981 sxn = 2, syn = 0; // sx = dy, sy = dx
1982 else if (transform == 6) // flip y
1983 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1984 else if (transform == 7) // flip y then rotate by 90 degrees ccw
1985 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1987 for (u32 dy=0; dy<dstdim.Height; dy++)
1988 for (u32 dx=0; dx<dstdim.Width; dx++)
1990 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1991 u32 sx = entries[sxn];
1992 u32 sy = entries[syn];
1993 video::SColor c = src->getPixel(sx,sy);
1994 dst->setPixel(dx,dy,c);
1998 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2000 if (isKnownSourceImage("override_normal.png"))
2001 return getTexture("override_normal.png");
2002 std::string fname_base = name;
2003 std::string normal_ext = "_normal.png";
2004 size_t pos = fname_base.find(".");
2005 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2006 if (isKnownSourceImage(fname_normal)) {
2007 // look for image extension and replace it
2009 while ((i = fname_base.find(".", i)) != std::string::npos) {
2010 fname_base.replace(i, 4, normal_ext);
2011 i += normal_ext.length();
2013 return getTexture(fname_base);
2018 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2020 video::IVideoDriver *driver = m_device->getVideoDriver();
2021 video::SColor c(0, 0, 0, 0);
2022 video::ITexture *texture = getTexture(name);
2023 video::IImage *image = driver->createImage(texture,
2024 core::position2d<s32>(0, 0),
2025 texture->getOriginalSize());
2030 core::dimension2d<u32> dim = image->getDimension();
2033 step = dim.Width / 16;
2034 for (u16 x = 0; x < dim.Width; x += step) {
2035 for (u16 y = 0; y < dim.Width; y += step) {
2036 c = image->getPixel(x,y);
2037 if (c.getAlpha() > 0) {
2047 c.setRed(tR / total);
2048 c.setGreen(tG / total);
2049 c.setBlue(tB / total);
2056 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2058 std::string tname = "__shaderFlagsTexture";
2059 tname += normalmap_present ? "1" : "0";
2061 if (isKnownSourceImage(tname)) {
2062 return getTexture(tname);
2064 video::IVideoDriver *driver = m_device->getVideoDriver();
2065 video::IImage *flags_image = driver->createImage(
2066 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2067 sanity_check(flags_image != NULL);
2068 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2069 flags_image->setPixel(0, 0, c);
2070 insertSourceImage(tname, flags_image);
2071 flags_image->drop();
2072 return getTexture(tname);