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 video::IImage* generateImage(const std::string &name);
383 video::ITexture* getNormalTexture(const std::string &name);
384 video::SColor getTextureAverageColor(const std::string &name);
385 video::ITexture *getShaderFlagsTexture(bool normamap_present);
389 // The id of the thread that is allowed to use irrlicht directly
390 threadid_t m_main_thread;
391 // The irrlicht device
392 IrrlichtDevice *m_device;
394 // Cache of source images
395 // This should be only accessed from the main thread
396 SourceImageCache m_sourcecache;
398 // Generate a texture
399 u32 generateTexture(const std::string &name);
401 // Generate image based on a string like "stone.png" or "[crack:1:0".
402 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
403 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
405 // Thread-safe cache of what source images are known (true = known)
406 MutexedMap<std::string, bool> m_source_image_existence;
408 // A texture id is index in this array.
409 // The first position contains a NULL texture.
410 std::vector<TextureInfo> m_textureinfo_cache;
411 // Maps a texture name to an index in the former.
412 std::map<std::string, u32> m_name_to_id;
413 // The two former containers are behind this mutex
414 Mutex m_textureinfo_cache_mutex;
416 // Queued texture fetches (to be processed by the main thread)
417 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
419 // Textures that have been overwritten with other ones
420 // but can't be deleted because the ITexture* might still be used
421 std::vector<video::ITexture*> m_texture_trash;
423 // Cached settings needed for making textures from meshes
424 bool m_setting_trilinear_filter;
425 bool m_setting_bilinear_filter;
426 bool m_setting_anisotropic_filter;
429 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
431 return new TextureSource(device);
434 TextureSource::TextureSource(IrrlichtDevice *device):
437 assert(m_device); // Pre-condition
439 m_main_thread = thr_get_current_thread_id();
441 // Add a NULL TextureInfo as the first index, named ""
442 m_textureinfo_cache.push_back(TextureInfo(""));
443 m_name_to_id[""] = 0;
445 // Cache some settings
446 // Note: Since this is only done once, the game must be restarted
447 // for these settings to take effect
448 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
449 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
450 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
453 TextureSource::~TextureSource()
455 video::IVideoDriver* driver = m_device->getVideoDriver();
457 unsigned int textures_before = driver->getTextureCount();
459 for (std::vector<TextureInfo>::iterator iter =
460 m_textureinfo_cache.begin();
461 iter != m_textureinfo_cache.end(); ++iter)
465 driver->removeTexture(iter->texture);
467 m_textureinfo_cache.clear();
469 for (std::vector<video::ITexture*>::iterator iter =
470 m_texture_trash.begin(); iter != m_texture_trash.end();
472 video::ITexture *t = *iter;
474 //cleanup trashed texture
475 driver->removeTexture(t);
478 infostream << "~TextureSource() "<< textures_before << "/"
479 << driver->getTextureCount() << std::endl;
482 u32 TextureSource::getTextureId(const std::string &name)
484 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
488 See if texture already exists
490 MutexAutoLock lock(m_textureinfo_cache_mutex);
491 std::map<std::string, u32>::iterator n;
492 n = m_name_to_id.find(name);
493 if (n != m_name_to_id.end())
502 if (thr_is_current_thread(m_main_thread))
504 return generateTexture(name);
508 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
510 // We're gonna ask the result to be put into here
511 static ResultQueue<std::string, u32, u8, u8> result_queue;
513 // Throw a request in
514 m_get_texture_queue.add(name, 0, 0, &result_queue);
516 /*infostream<<"Waiting for texture from main thread, name=\""
517 <<name<<"\""<<std::endl;*/
522 // Wait result for a second
523 GetResult<std::string, u32, u8, u8>
524 result = result_queue.pop_front(1000);
526 if (result.key == name) {
531 catch(ItemNotFoundException &e)
533 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
538 infostream<<"getTextureId(): Failed"<<std::endl;
543 // Draw an image on top of an another one, using the alpha channel of the
545 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
546 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
548 // Like blit_with_alpha, but only modifies destination pixels that
550 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
551 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
553 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
554 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
555 // color alpha with the destination alpha.
556 // Otherwise, any pixels that are not fully transparent get the color alpha.
557 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
558 const video::SColor &color, int ratio, bool keep_alpha);
560 // paint a texture using the given color
561 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
562 const video::SColor &color);
564 // Apply a mask to an image
565 static void apply_mask(video::IImage *mask, video::IImage *dst,
566 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
568 // Draw or overlay a crack
569 static void draw_crack(video::IImage *crack, video::IImage *dst,
570 bool use_overlay, s32 frame_count, s32 progression,
571 video::IVideoDriver *driver);
574 void brighten(video::IImage *image);
575 // Parse a transform name
576 u32 parseImageTransform(const std::string& s);
577 // Apply transform to image dimension
578 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
579 // Apply transform to image data
580 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
583 This method generates all the textures
585 u32 TextureSource::generateTexture(const std::string &name)
587 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
589 // Empty name means texture 0
591 infostream<<"generateTexture(): name is empty"<<std::endl;
597 See if texture already exists
599 MutexAutoLock lock(m_textureinfo_cache_mutex);
600 std::map<std::string, u32>::iterator n;
601 n = m_name_to_id.find(name);
602 if (n != m_name_to_id.end()) {
608 Calling only allowed from main thread
610 if (!thr_is_current_thread(m_main_thread)) {
611 errorstream<<"TextureSource::generateTexture() "
612 "called not from main thread"<<std::endl;
616 video::IVideoDriver *driver = m_device->getVideoDriver();
617 sanity_check(driver);
619 video::IImage *img = generateImage(name);
621 video::ITexture *tex = NULL;
625 img = Align2Npot2(img, driver);
627 // Create texture from resulting image
628 tex = driver->addTexture(name.c_str(), img);
629 guiScalingCache(io::path(name.c_str()), driver, img);
634 Add texture to caches (add NULL textures too)
637 MutexAutoLock lock(m_textureinfo_cache_mutex);
639 u32 id = m_textureinfo_cache.size();
640 TextureInfo ti(name, tex);
641 m_textureinfo_cache.push_back(ti);
642 m_name_to_id[name] = id;
647 std::string TextureSource::getTextureName(u32 id)
649 MutexAutoLock lock(m_textureinfo_cache_mutex);
651 if (id >= m_textureinfo_cache.size())
653 errorstream<<"TextureSource::getTextureName(): id="<<id
654 <<" >= m_textureinfo_cache.size()="
655 <<m_textureinfo_cache.size()<<std::endl;
659 return m_textureinfo_cache[id].name;
662 video::ITexture* TextureSource::getTexture(u32 id)
664 MutexAutoLock lock(m_textureinfo_cache_mutex);
666 if (id >= m_textureinfo_cache.size())
669 return m_textureinfo_cache[id].texture;
672 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
674 u32 actual_id = getTextureId(name);
678 return getTexture(actual_id);
681 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
683 return getTexture(name + "^[applyfiltersformesh", id);
686 void TextureSource::processQueue()
691 //NOTE this is only thread safe for ONE consumer thread!
692 if (!m_get_texture_queue.empty())
694 GetRequest<std::string, u32, u8, u8>
695 request = m_get_texture_queue.pop();
697 /*infostream<<"TextureSource::processQueue(): "
698 <<"got texture request with "
699 <<"name=\""<<request.key<<"\""
702 m_get_texture_queue.pushResult(request, generateTexture(request.key));
706 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
708 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
710 sanity_check(thr_is_current_thread(m_main_thread));
712 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
713 m_source_image_existence.set(name, true);
716 void TextureSource::rebuildImagesAndTextures()
718 MutexAutoLock lock(m_textureinfo_cache_mutex);
720 video::IVideoDriver* driver = m_device->getVideoDriver();
721 sanity_check(driver);
724 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
725 TextureInfo *ti = &m_textureinfo_cache[i];
726 video::IImage *img = generateImage(ti->name);
728 img = Align2Npot2(img, driver);
729 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
730 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
732 // Create texture from resulting image
733 video::ITexture *t = NULL;
735 t = driver->addTexture(ti->name.c_str(), img);
736 guiScalingCache(io::path(ti->name.c_str()), driver, img);
739 video::ITexture *t_old = ti->texture;
744 m_texture_trash.push_back(t_old);
748 video::ITexture* TextureSource::generateTextureFromMesh(
749 const TextureFromMeshParams ¶ms)
751 video::IVideoDriver *driver = m_device->getVideoDriver();
752 sanity_check(driver);
755 const GLubyte* renderstr = glGetString(GL_RENDERER);
756 std::string renderer((char*) renderstr);
758 // use no render to texture hack
760 (renderer.find("Adreno") != std::string::npos) ||
761 (renderer.find("Mali") != std::string::npos) ||
762 (renderer.find("Immersion") != std::string::npos) ||
763 (renderer.find("Tegra") != std::string::npos) ||
764 g_settings->getBool("inventory_image_hack")
766 // Get a scene manager
767 scene::ISceneManager *smgr_main = m_device->getSceneManager();
768 sanity_check(smgr_main);
769 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
772 const float scaling = 0.2;
774 scene::IMeshSceneNode* meshnode =
775 smgr->addMeshSceneNode(params.mesh, NULL,
776 -1, v3f(0,0,0), v3f(0,0,0),
777 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
778 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
779 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
780 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
781 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
782 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
784 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
785 params.camera_position, params.camera_lookat);
786 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
787 camera->setProjectionMatrix(params.camera_projection_matrix, false);
789 smgr->setAmbientLight(params.ambient_light);
790 smgr->addLightSceneNode(0,
791 params.light_position,
793 params.light_radius*scaling);
795 core::dimension2d<u32> screen = driver->getScreenSize();
798 driver->beginScene(true, true, video::SColor(0,0,0,0));
799 driver->clearZBuffer();
802 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
804 irr::video::IImage* rawImage =
805 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
807 u8* pixels = static_cast<u8*>(rawImage->lock());
814 core::rect<s32> source(
815 screen.Width /2 - (screen.Width * (scaling / 2)),
816 screen.Height/2 - (screen.Height * (scaling / 2)),
817 screen.Width /2 + (screen.Width * (scaling / 2)),
818 screen.Height/2 + (screen.Height * (scaling / 2))
821 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
822 partsize.Width, partsize.Height, GL_RGBA,
823 GL_UNSIGNED_BYTE, pixels);
827 // Drop scene manager
830 unsigned int pixelcount = partsize.Width*partsize.Height;
833 for (unsigned int i=0; i < pixelcount; i++) {
851 video::IImage* inventory_image =
852 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
854 rawImage->copyToScaling(inventory_image);
857 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
859 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
860 inventory_image->drop();
863 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
867 driver->makeColorKeyTexture(rtt, v2s32(0,0));
869 if (params.delete_texture_on_shutdown)
870 m_texture_trash.push_back(rtt);
876 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
878 static bool warned = false;
881 errorstream<<"TextureSource::generateTextureFromMesh(): "
882 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
888 // Create render target texture
889 video::ITexture *rtt = driver->addRenderTargetTexture(
890 params.dim, params.rtt_texture_name.c_str(),
891 video::ECF_A8R8G8B8);
894 errorstream<<"TextureSource::generateTextureFromMesh(): "
895 <<"addRenderTargetTexture returned NULL."<<std::endl;
900 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
901 driver->removeTexture(rtt);
902 errorstream<<"TextureSource::generateTextureFromMesh(): "
903 <<"failed to set render target"<<std::endl;
907 // Get a scene manager
908 scene::ISceneManager *smgr_main = m_device->getSceneManager();
910 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
913 scene::IMeshSceneNode* meshnode =
914 smgr->addMeshSceneNode(params.mesh, NULL,
915 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
916 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
917 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
918 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
919 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
920 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
922 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
923 params.camera_position, params.camera_lookat);
924 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
925 camera->setProjectionMatrix(params.camera_projection_matrix, false);
927 smgr->setAmbientLight(params.ambient_light);
928 smgr->addLightSceneNode(0,
929 params.light_position,
931 params.light_radius);
934 driver->beginScene(true, true, video::SColor(0,0,0,0));
938 // Drop scene manager
941 // Unset render target
942 driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
944 if (params.delete_texture_on_shutdown)
945 m_texture_trash.push_back(rtt);
950 video::IImage* TextureSource::generateImage(const std::string &name)
952 // Get the base image
954 const char separator = '^';
955 const char escape = '\\';
956 const char paren_open = '(';
957 const char paren_close = ')';
959 // Find last separator in the name
960 s32 last_separator_pos = -1;
962 for (s32 i = name.size() - 1; i >= 0; i--) {
963 if (i > 0 && name[i-1] == escape)
967 if (paren_bal == 0) {
968 last_separator_pos = i;
969 i = -1; // break out of loop
973 if (paren_bal == 0) {
974 errorstream << "generateImage(): unbalanced parentheses"
975 << "(extranous '(') while generating texture \""
976 << name << "\"" << std::endl;
989 errorstream << "generateImage(): unbalanced parentheses"
990 << "(missing matching '(') while generating texture \""
991 << name << "\"" << std::endl;
996 video::IImage *baseimg = NULL;
999 If separator was found, make the base image
1000 using a recursive call.
1002 if (last_separator_pos != -1) {
1003 baseimg = generateImage(name.substr(0, last_separator_pos));
1007 video::IVideoDriver* driver = m_device->getVideoDriver();
1008 sanity_check(driver);
1011 Parse out the last part of the name of the image and act
1015 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1018 If this name is enclosed in parentheses, generate it
1019 and blit it onto the base image
1021 if (last_part_of_name[0] == paren_open
1022 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1023 std::string name2 = last_part_of_name.substr(1,
1024 last_part_of_name.size() - 2);
1025 video::IImage *tmp = generateImage(name2);
1027 errorstream << "generateImage(): "
1028 "Failed to generate \"" << name2 << "\""
1032 core::dimension2d<u32> dim = tmp->getDimension();
1034 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1039 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1040 // Generate image according to part of name
1041 errorstream << "generateImage(): "
1042 "Failed to generate \"" << last_part_of_name << "\""
1046 // If no resulting image, print a warning
1047 if (baseimg == NULL) {
1048 errorstream << "generateImage(): baseimg is NULL (attempted to"
1049 " create texture \"" << name << "\")" << std::endl;
1056 #include <GLES/gl.h>
1058 * Check and align image to npot2 if required by hardware
1059 * @param image image to check for npot2 alignment
1060 * @param driver driver to use for image operations
1061 * @return image or copy of image aligned to npot2
1063 video::IImage * Align2Npot2(video::IImage * image,
1064 video::IVideoDriver* driver)
1066 if (image == NULL) {
1070 core::dimension2d<u32> dim = image->getDimension();
1072 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1073 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1077 unsigned int height = npot2(dim.Height);
1078 unsigned int width = npot2(dim.Width);
1080 if ((dim.Height == height) &&
1081 (dim.Width == width)) {
1085 if (dim.Height > height) {
1089 if (dim.Width > width) {
1093 video::IImage *targetimage =
1094 driver->createImage(video::ECF_A8R8G8B8,
1095 core::dimension2d<u32>(width, height));
1097 if (targetimage != NULL) {
1098 image->copyToScaling(targetimage);
1106 static std::string unescape_string(const std::string &str, const char esc = '\\')
1109 size_t pos = 0, cpos;
1110 out.reserve(str.size());
1112 cpos = str.find_first_of(esc, pos);
1113 if (cpos == std::string::npos) {
1114 out += str.substr(pos);
1117 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1123 bool TextureSource::generateImagePart(std::string part_of_name,
1124 video::IImage *& baseimg)
1126 const char escape = '\\'; // same as in generateImage()
1127 video::IVideoDriver* driver = m_device->getVideoDriver();
1128 sanity_check(driver);
1130 // Stuff starting with [ are special commands
1131 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1133 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1135 image = Align2Npot2(image, driver);
1137 if (image == NULL) {
1138 if (part_of_name != "") {
1139 if (part_of_name.find("_normal.png") == std::string::npos){
1140 errorstream<<"generateImage(): Could not load image \""
1141 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1142 errorstream<<"generateImage(): Creating a dummy"
1143 <<" image for \""<<part_of_name<<"\""<<std::endl;
1145 infostream<<"generateImage(): Could not load normal map \""
1146 <<part_of_name<<"\""<<std::endl;
1147 infostream<<"generateImage(): Creating a dummy"
1148 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1152 // Just create a dummy image
1153 //core::dimension2d<u32> dim(2,2);
1154 core::dimension2d<u32> dim(1,1);
1155 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1156 sanity_check(image != NULL);
1157 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1158 image->setPixel(1,0, video::SColor(255,0,255,0));
1159 image->setPixel(0,1, video::SColor(255,0,0,255));
1160 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1161 image->setPixel(0,0, video::SColor(255,myrand()%256,
1162 myrand()%256,myrand()%256));
1163 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1164 myrand()%256,myrand()%256));
1165 image->setPixel(0,1, video::SColor(255,myrand()%256,
1166 myrand()%256,myrand()%256));
1167 image->setPixel(1,1, video::SColor(255,myrand()%256,
1168 myrand()%256,myrand()%256));*/
1171 // If base image is NULL, load as base.
1172 if (baseimg == NULL)
1174 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1176 Copy it this way to get an alpha channel.
1177 Otherwise images with alpha cannot be blitted on
1178 images that don't have alpha in the original file.
1180 core::dimension2d<u32> dim = image->getDimension();
1181 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1182 image->copyTo(baseimg);
1184 // Else blit on base.
1187 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1188 // Size of the copied area
1189 core::dimension2d<u32> dim = image->getDimension();
1190 //core::dimension2d<u32> dim(16,16);
1191 // Position to copy the blitted to in the base image
1192 core::position2d<s32> pos_to(0,0);
1193 // Position to copy the blitted from in the blitted image
1194 core::position2d<s32> pos_from(0,0);
1196 /*image->copyToWithAlpha(baseimg, pos_to,
1197 core::rect<s32>(pos_from, dim),
1198 video::SColor(255,255,255,255),
1201 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1202 if (dim == dim_dst) {
1203 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1204 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1205 // Upscale overlying image
1206 video::IImage* scaled_image = m_device->getVideoDriver()->
1207 createImage(video::ECF_A8R8G8B8, dim_dst);
1208 image->copyToScaling(scaled_image);
1210 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1211 scaled_image->drop();
1213 // Upscale base image
1214 video::IImage* scaled_base = m_device->getVideoDriver()->
1215 createImage(video::ECF_A8R8G8B8, dim);
1216 baseimg->copyToScaling(scaled_base);
1218 baseimg = scaled_base;
1220 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1228 // A special texture modification
1230 /*infostream<<"generateImage(): generating special "
1231 <<"modification \""<<part_of_name<<"\""
1237 Adds a cracking texture
1238 N = animation frame count, P = crack progression
1240 if (str_starts_with(part_of_name, "[crack"))
1242 if (baseimg == NULL) {
1243 errorstream<<"generateImagePart(): baseimg == NULL "
1244 <<"for part_of_name=\""<<part_of_name
1245 <<"\", cancelling."<<std::endl;
1249 // Crack image number and overlay option
1250 bool use_overlay = (part_of_name[6] == 'o');
1251 Strfnd sf(part_of_name);
1253 s32 frame_count = stoi(sf.next(":"));
1254 s32 progression = stoi(sf.next(":"));
1256 if (progression >= 0) {
1260 It is an image with a number of cracking stages
1263 video::IImage *img_crack = m_sourcecache.getOrLoad(
1264 "crack_anylength.png", m_device);
1267 draw_crack(img_crack, baseimg,
1268 use_overlay, frame_count,
1269 progression, driver);
1275 [combine:WxH:X,Y=filename:X,Y=filename2
1276 Creates a bigger texture from any amount of smaller ones
1278 else if (str_starts_with(part_of_name, "[combine"))
1280 Strfnd sf(part_of_name);
1282 u32 w0 = stoi(sf.next("x"));
1283 u32 h0 = stoi(sf.next(":"));
1284 core::dimension2d<u32> dim(w0,h0);
1285 if (baseimg == NULL) {
1286 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1287 baseimg->fill(video::SColor(0,0,0,0));
1289 while (sf.at_end() == false) {
1290 u32 x = stoi(sf.next(","));
1291 u32 y = stoi(sf.next("="));
1292 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1293 infostream<<"Adding \""<<filename
1294 <<"\" to combined ("<<x<<","<<y<<")"
1296 video::IImage *img = generateImage(filename);
1298 core::dimension2d<u32> dim = img->getDimension();
1299 infostream<<"Size "<<dim.Width
1300 <<"x"<<dim.Height<<std::endl;
1301 core::position2d<s32> pos_base(x, y);
1302 video::IImage *img2 =
1303 driver->createImage(video::ECF_A8R8G8B8, dim);
1306 /*img2->copyToWithAlpha(baseimg, pos_base,
1307 core::rect<s32>(v2s32(0,0), dim),
1308 video::SColor(255,255,255,255),
1310 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1313 errorstream << "generateImagePart(): Failed to load image \""
1314 << filename << "\" for [combine" << std::endl;
1321 else if (str_starts_with(part_of_name, "[brighten"))
1323 if (baseimg == NULL) {
1324 errorstream<<"generateImagePart(): baseimg==NULL "
1325 <<"for part_of_name=\""<<part_of_name
1326 <<"\", cancelling."<<std::endl;
1334 Make image completely opaque.
1335 Used for the leaves texture when in old leaves mode, so
1336 that the transparent parts don't look completely black
1337 when simple alpha channel is used for rendering.
1339 else if (str_starts_with(part_of_name, "[noalpha"))
1341 if (baseimg == NULL){
1342 errorstream<<"generateImagePart(): baseimg==NULL "
1343 <<"for part_of_name=\""<<part_of_name
1344 <<"\", cancelling."<<std::endl;
1348 core::dimension2d<u32> dim = baseimg->getDimension();
1350 // Set alpha to full
1351 for (u32 y=0; y<dim.Height; y++)
1352 for (u32 x=0; x<dim.Width; x++)
1354 video::SColor c = baseimg->getPixel(x,y);
1356 baseimg->setPixel(x,y,c);
1361 Convert one color to transparent.
1363 else if (str_starts_with(part_of_name, "[makealpha:"))
1365 if (baseimg == NULL) {
1366 errorstream<<"generateImagePart(): baseimg == NULL "
1367 <<"for part_of_name=\""<<part_of_name
1368 <<"\", cancelling."<<std::endl;
1372 Strfnd sf(part_of_name.substr(11));
1373 u32 r1 = stoi(sf.next(","));
1374 u32 g1 = stoi(sf.next(","));
1375 u32 b1 = stoi(sf.next(""));
1377 core::dimension2d<u32> dim = baseimg->getDimension();
1379 /*video::IImage *oldbaseimg = baseimg;
1380 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1381 oldbaseimg->copyTo(baseimg);
1382 oldbaseimg->drop();*/
1384 // Set alpha to full
1385 for (u32 y=0; y<dim.Height; y++)
1386 for (u32 x=0; x<dim.Width; x++)
1388 video::SColor c = baseimg->getPixel(x,y);
1390 u32 g = c.getGreen();
1391 u32 b = c.getBlue();
1392 if (!(r == r1 && g == g1 && b == b1))
1395 baseimg->setPixel(x,y,c);
1400 Rotates and/or flips the image.
1402 N can be a number (between 0 and 7) or a transform name.
1403 Rotations are counter-clockwise.
1405 1 R90 rotate by 90 degrees
1406 2 R180 rotate by 180 degrees
1407 3 R270 rotate by 270 degrees
1409 5 FXR90 flip X then rotate by 90 degrees
1411 7 FYR90 flip Y then rotate by 90 degrees
1413 Note: Transform names can be concatenated to produce
1414 their product (applies the first then the second).
1415 The resulting transform will be equivalent to one of the
1416 eight existing ones, though (see: dihedral group).
1418 else if (str_starts_with(part_of_name, "[transform"))
1420 if (baseimg == NULL) {
1421 errorstream<<"generateImagePart(): baseimg == NULL "
1422 <<"for part_of_name=\""<<part_of_name
1423 <<"\", cancelling."<<std::endl;
1427 u32 transform = parseImageTransform(part_of_name.substr(10));
1428 core::dimension2d<u32> dim = imageTransformDimension(
1429 transform, baseimg->getDimension());
1430 video::IImage *image = driver->createImage(
1431 baseimg->getColorFormat(), dim);
1432 sanity_check(image != NULL);
1433 imageTransform(transform, baseimg, image);
1438 [inventorycube{topimage{leftimage{rightimage
1439 In every subimage, replace ^ with &.
1440 Create an "inventory cube".
1441 NOTE: This should be used only on its own.
1442 Example (a grass block (not actually used in game):
1443 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1445 else if (str_starts_with(part_of_name, "[inventorycube"))
1447 if (baseimg != NULL){
1448 errorstream<<"generateImagePart(): baseimg != NULL "
1449 <<"for part_of_name=\""<<part_of_name
1450 <<"\", cancelling."<<std::endl;
1454 str_replace(part_of_name, '&', '^');
1455 Strfnd sf(part_of_name);
1457 std::string imagename_top = sf.next("{");
1458 std::string imagename_left = sf.next("{");
1459 std::string imagename_right = sf.next("{");
1461 // Generate images for the faces of the cube
1462 video::IImage *img_top = generateImage(imagename_top);
1463 video::IImage *img_left = generateImage(imagename_left);
1464 video::IImage *img_right = generateImage(imagename_right);
1466 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1467 errorstream << "generateImagePart(): Failed to create textures"
1468 << " for inventorycube \"" << part_of_name << "\""
1470 baseimg = generateImage(imagename_top);
1475 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1476 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1478 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1479 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1481 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1482 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1485 // Create textures from images
1486 video::ITexture *texture_top = driver->addTexture(
1487 (imagename_top + "__temp__").c_str(), img_top);
1488 video::ITexture *texture_left = driver->addTexture(
1489 (imagename_left + "__temp__").c_str(), img_left);
1490 video::ITexture *texture_right = driver->addTexture(
1491 (imagename_right + "__temp__").c_str(), img_right);
1492 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1500 Draw a cube mesh into a render target texture
1502 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1503 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1504 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1505 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1506 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1507 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1508 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1509 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1511 TextureFromMeshParams params;
1513 params.dim.set(64, 64);
1514 params.rtt_texture_name = part_of_name + "_RTT";
1515 // We will delete the rtt texture ourselves
1516 params.delete_texture_on_shutdown = false;
1517 params.camera_position.set(0, 1.0, -1.5);
1518 params.camera_position.rotateXZBy(45);
1519 params.camera_lookat.set(0, 0, 0);
1520 // Set orthogonal projection
1521 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1522 1.65, 1.65, 0, 100);
1524 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1525 params.light_position.set(10, 100, -50);
1526 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1527 params.light_radius = 1000;
1529 video::ITexture *rtt = generateTextureFromMesh(params);
1535 driver->removeTexture(texture_top);
1536 driver->removeTexture(texture_left);
1537 driver->removeTexture(texture_right);
1540 baseimg = generateImage(imagename_top);
1544 // Create image of render target
1545 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1546 FATAL_ERROR_IF(!image, "Could not create image of render target");
1549 driver->removeTexture(rtt);
1551 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1554 image->copyTo(baseimg);
1559 [lowpart:percent:filename
1560 Adds the lower part of a texture
1562 else if (str_starts_with(part_of_name, "[lowpart:"))
1564 Strfnd sf(part_of_name);
1566 u32 percent = stoi(sf.next(":"));
1567 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1569 if (baseimg == NULL)
1570 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1571 video::IImage *img = generateImage(filename);
1574 core::dimension2d<u32> dim = img->getDimension();
1575 core::position2d<s32> pos_base(0, 0);
1576 video::IImage *img2 =
1577 driver->createImage(video::ECF_A8R8G8B8, dim);
1580 core::position2d<s32> clippos(0, 0);
1581 clippos.Y = dim.Height * (100-percent) / 100;
1582 core::dimension2d<u32> clipdim = dim;
1583 clipdim.Height = clipdim.Height * percent / 100 + 1;
1584 core::rect<s32> cliprect(clippos, clipdim);
1585 img2->copyToWithAlpha(baseimg, pos_base,
1586 core::rect<s32>(v2s32(0,0), dim),
1587 video::SColor(255,255,255,255),
1594 Crops a frame of a vertical animation.
1595 N = frame count, I = frame index
1597 else if (str_starts_with(part_of_name, "[verticalframe:"))
1599 Strfnd sf(part_of_name);
1601 u32 frame_count = stoi(sf.next(":"));
1602 u32 frame_index = stoi(sf.next(":"));
1604 if (baseimg == NULL){
1605 errorstream<<"generateImagePart(): baseimg != NULL "
1606 <<"for part_of_name=\""<<part_of_name
1607 <<"\", cancelling."<<std::endl;
1611 v2u32 frame_size = baseimg->getDimension();
1612 frame_size.Y /= frame_count;
1614 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1617 errorstream<<"generateImagePart(): Could not create image "
1618 <<"for part_of_name=\""<<part_of_name
1619 <<"\", cancelling."<<std::endl;
1623 // Fill target image with transparency
1624 img->fill(video::SColor(0,0,0,0));
1626 core::dimension2d<u32> dim = frame_size;
1627 core::position2d<s32> pos_dst(0, 0);
1628 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1629 baseimg->copyToWithAlpha(img, pos_dst,
1630 core::rect<s32>(pos_src, dim),
1631 video::SColor(255,255,255,255),
1639 Applies a mask to an image
1641 else if (str_starts_with(part_of_name, "[mask:"))
1643 if (baseimg == NULL) {
1644 errorstream << "generateImage(): baseimg == NULL "
1645 << "for part_of_name=\"" << part_of_name
1646 << "\", cancelling." << std::endl;
1649 Strfnd sf(part_of_name);
1651 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1653 video::IImage *img = generateImage(filename);
1655 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1656 img->getDimension());
1659 errorstream << "generateImage(): Failed to load \""
1660 << filename << "\".";
1665 multiplys a given color to any pixel of an image
1666 color = color as ColorString
1668 else if (str_starts_with(part_of_name, "[multiply:")) {
1669 Strfnd sf(part_of_name);
1671 std::string color_str = sf.next(":");
1673 if (baseimg == NULL) {
1674 errorstream << "generateImagePart(): baseimg != NULL "
1675 << "for part_of_name=\"" << part_of_name
1676 << "\", cancelling." << std::endl;
1680 video::SColor color;
1682 if (!parseColorString(color_str, color, false))
1685 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1689 Overlays image with given color
1690 color = color as ColorString
1692 else if (str_starts_with(part_of_name, "[colorize:"))
1694 Strfnd sf(part_of_name);
1696 std::string color_str = sf.next(":");
1697 std::string ratio_str = sf.next(":");
1699 if (baseimg == NULL) {
1700 errorstream << "generateImagePart(): baseimg != NULL "
1701 << "for part_of_name=\"" << part_of_name
1702 << "\", cancelling." << std::endl;
1706 video::SColor color;
1708 bool keep_alpha = false;
1710 if (!parseColorString(color_str, color, false))
1713 if (is_number(ratio_str))
1714 ratio = mystoi(ratio_str, 0, 255);
1715 else if (ratio_str == "alpha")
1718 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1721 [applyfiltersformesh
1724 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1726 // Apply the "clean transparent" filter, if configured.
1727 if (g_settings->getBool("texture_clean_transparent"))
1728 imageCleanTransparent(baseimg, 127);
1730 /* Upscale textures to user's requested minimum size. This is a trick to make
1731 * filters look as good on low-res textures as on high-res ones, by making
1732 * low-res textures BECOME high-res ones. This is helpful for worlds that
1733 * mix high- and low-res textures, or for mods with least-common-denominator
1734 * textures that don't have the resources to offer high-res alternatives.
1736 s32 scaleto = g_settings->getS32("texture_min_size");
1738 const core::dimension2d<u32> dim = baseimg->getDimension();
1740 /* Calculate scaling needed to make the shortest texture dimension
1741 * equal to the target minimum. If e.g. this is a vertical frames
1742 * animation, the short dimension will be the real size.
1744 if ((dim.Width == 0) || (dim.Height == 0)) {
1745 errorstream << "generateImagePart(): Illegal 0 dimension "
1746 << "for part_of_name=\""<< part_of_name
1747 << "\", cancelling." << std::endl;
1750 u32 xscale = scaleto / dim.Width;
1751 u32 yscale = scaleto / dim.Height;
1752 u32 scale = (xscale > yscale) ? xscale : yscale;
1754 // Never downscale; only scale up by 2x or more.
1756 u32 w = scale * dim.Width;
1757 u32 h = scale * dim.Height;
1758 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1759 video::IImage *newimg = driver->createImage(
1760 baseimg->getColorFormat(), newdim);
1761 baseimg->copyToScaling(newimg);
1769 Resizes the base image to the given dimensions
1771 else if (str_starts_with(part_of_name, "[resize"))
1773 if (baseimg == NULL) {
1774 errorstream << "generateImagePart(): baseimg == NULL "
1775 << "for part_of_name=\""<< part_of_name
1776 << "\", cancelling." << std::endl;
1780 Strfnd sf(part_of_name);
1782 u32 width = stoi(sf.next("x"));
1783 u32 height = stoi(sf.next(""));
1784 core::dimension2d<u32> dim(width, height);
1786 video::IImage* image = m_device->getVideoDriver()->
1787 createImage(video::ECF_A8R8G8B8, dim);
1788 baseimg->copyToScaling(image);
1794 Makes the base image transparent according to the given ratio.
1795 R must be between 0 and 255.
1796 0 means totally transparent.
1797 255 means totally opaque.
1799 else if (str_starts_with(part_of_name, "[opacity:")) {
1800 if (baseimg == NULL) {
1801 errorstream << "generateImagePart(): baseimg == NULL "
1802 << "for part_of_name=\"" << part_of_name
1803 << "\", cancelling." << std::endl;
1807 Strfnd sf(part_of_name);
1810 u32 ratio = mystoi(sf.next(""), 0, 255);
1812 core::dimension2d<u32> dim = baseimg->getDimension();
1814 for (u32 y = 0; y < dim.Height; y++)
1815 for (u32 x = 0; x < dim.Width; x++)
1817 video::SColor c = baseimg->getPixel(x, y);
1818 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1819 baseimg->setPixel(x, y, c);
1824 Inverts the given channels of the base image.
1825 Mode may contain the characters "r", "g", "b", "a".
1826 Only the channels that are mentioned in the mode string
1829 else if (str_starts_with(part_of_name, "[invert:")) {
1830 if (baseimg == NULL) {
1831 errorstream << "generateImagePart(): baseimg == NULL "
1832 << "for part_of_name=\"" << part_of_name
1833 << "\", cancelling." << std::endl;
1837 Strfnd sf(part_of_name);
1840 std::string mode = sf.next("");
1842 if (mode.find("a") != std::string::npos)
1843 mask |= 0xff000000UL;
1844 if (mode.find("r") != std::string::npos)
1845 mask |= 0x00ff0000UL;
1846 if (mode.find("g") != std::string::npos)
1847 mask |= 0x0000ff00UL;
1848 if (mode.find("b") != std::string::npos)
1849 mask |= 0x000000ffUL;
1851 core::dimension2d<u32> dim = baseimg->getDimension();
1853 for (u32 y = 0; y < dim.Height; y++)
1854 for (u32 x = 0; x < dim.Width; x++)
1856 video::SColor c = baseimg->getPixel(x, y);
1858 baseimg->setPixel(x, y, c);
1863 Retrieves a tile at position X,Y (in tiles)
1864 from the base image it assumes to be a
1865 tilesheet with dimensions W,H (in tiles).
1867 else if (part_of_name.substr(0,7) == "[sheet:") {
1868 if (baseimg == NULL) {
1869 errorstream << "generateImagePart(): baseimg != NULL "
1870 << "for part_of_name=\"" << part_of_name
1871 << "\", cancelling." << std::endl;
1875 Strfnd sf(part_of_name);
1877 u32 w0 = stoi(sf.next("x"));
1878 u32 h0 = stoi(sf.next(":"));
1879 u32 x0 = stoi(sf.next(","));
1880 u32 y0 = stoi(sf.next(":"));
1882 core::dimension2d<u32> img_dim = baseimg->getDimension();
1883 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1885 video::IImage *img = driver->createImage(
1886 video::ECF_A8R8G8B8, tile_dim);
1888 errorstream << "generateImagePart(): Could not create image "
1889 << "for part_of_name=\"" << part_of_name
1890 << "\", cancelling." << std::endl;
1894 img->fill(video::SColor(0,0,0,0));
1895 v2u32 vdim(tile_dim);
1896 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1897 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1898 video::SColor(255,255,255,255), NULL);
1906 errorstream << "generateImagePart(): Invalid "
1907 " modification: \"" << part_of_name << "\"" << std::endl;
1915 Draw an image on top of an another one, using the alpha channel of the
1918 This exists because IImage::copyToWithAlpha() doesn't seem to always
1921 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1922 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1924 for (u32 y0=0; y0<size.Y; y0++)
1925 for (u32 x0=0; x0<size.X; x0++)
1927 s32 src_x = src_pos.X + x0;
1928 s32 src_y = src_pos.Y + y0;
1929 s32 dst_x = dst_pos.X + x0;
1930 s32 dst_y = dst_pos.Y + y0;
1931 video::SColor src_c = src->getPixel(src_x, src_y);
1932 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1933 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1934 dst->setPixel(dst_x, dst_y, dst_c);
1939 Draw an image on top of an another one, using the alpha channel of the
1940 source image; only modify fully opaque pixels in destinaion
1942 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1943 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1945 for (u32 y0=0; y0<size.Y; y0++)
1946 for (u32 x0=0; x0<size.X; x0++)
1948 s32 src_x = src_pos.X + x0;
1949 s32 src_y = src_pos.Y + y0;
1950 s32 dst_x = dst_pos.X + x0;
1951 s32 dst_y = dst_pos.Y + y0;
1952 video::SColor src_c = src->getPixel(src_x, src_y);
1953 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1954 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1956 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1957 dst->setPixel(dst_x, dst_y, dst_c);
1962 // This function has been disabled because it is currently unused.
1963 // Feel free to re-enable if you find it handy.
1966 Draw an image on top of an another one, using the specified ratio
1967 modify all partially-opaque pixels in the destination.
1969 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1970 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1972 for (u32 y0 = 0; y0 < size.Y; y0++)
1973 for (u32 x0 = 0; x0 < size.X; x0++)
1975 s32 src_x = src_pos.X + x0;
1976 s32 src_y = src_pos.Y + y0;
1977 s32 dst_x = dst_pos.X + x0;
1978 s32 dst_y = dst_pos.Y + y0;
1979 video::SColor src_c = src->getPixel(src_x, src_y);
1980 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1981 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1984 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1986 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1987 dst->setPixel(dst_x, dst_y, dst_c);
1994 Apply color to destination
1996 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1997 const video::SColor &color, int ratio, bool keep_alpha)
1999 u32 alpha = color.getAlpha();
2000 video::SColor dst_c;
2001 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2002 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2004 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2005 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2006 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2007 if (dst_alpha > 0) {
2008 dst_c.setAlpha(dst_alpha * alpha / 255);
2009 dst->setPixel(x, y, dst_c);
2012 } else { // replace the color including the alpha
2013 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2014 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2015 if (dst->getPixel(x, y).getAlpha() > 0)
2016 dst->setPixel(x, y, color);
2018 } else { // interpolate between the color and destination
2019 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2020 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2021 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2022 dst_c = dst->getPixel(x, y);
2023 if (dst_c.getAlpha() > 0) {
2024 dst_c = color.getInterpolated(dst_c, interp);
2025 dst->setPixel(x, y, dst_c);
2032 Apply color to destination
2034 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2035 const video::SColor &color)
2037 video::SColor dst_c;
2039 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2040 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2041 dst_c = dst->getPixel(x, y);
2044 (dst_c.getRed() * color.getRed()) / 255,
2045 (dst_c.getGreen() * color.getGreen()) / 255,
2046 (dst_c.getBlue() * color.getBlue()) / 255
2048 dst->setPixel(x, y, dst_c);
2053 Apply mask to destination
2055 static void apply_mask(video::IImage *mask, video::IImage *dst,
2056 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2058 for (u32 y0 = 0; y0 < size.Y; y0++) {
2059 for (u32 x0 = 0; x0 < size.X; x0++) {
2060 s32 mask_x = x0 + mask_pos.X;
2061 s32 mask_y = y0 + mask_pos.Y;
2062 s32 dst_x = x0 + dst_pos.X;
2063 s32 dst_y = y0 + dst_pos.Y;
2064 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2065 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2066 dst_c.color &= mask_c.color;
2067 dst->setPixel(dst_x, dst_y, dst_c);
2072 static void draw_crack(video::IImage *crack, video::IImage *dst,
2073 bool use_overlay, s32 frame_count, s32 progression,
2074 video::IVideoDriver *driver)
2076 // Dimension of destination image
2077 core::dimension2d<u32> dim_dst = dst->getDimension();
2078 // Dimension of original image
2079 core::dimension2d<u32> dim_crack = crack->getDimension();
2080 // Count of crack stages
2081 s32 crack_count = dim_crack.Height / dim_crack.Width;
2082 // Limit frame_count
2083 if (frame_count > (s32) dim_dst.Height)
2084 frame_count = dim_dst.Height;
2085 if (frame_count < 1)
2087 // Limit progression
2088 if (progression > crack_count-1)
2089 progression = crack_count-1;
2090 // Dimension of a single crack stage
2091 core::dimension2d<u32> dim_crack_cropped(
2095 // Dimension of the scaled crack stage,
2096 // which is the same as the dimension of a single destination frame
2097 core::dimension2d<u32> dim_crack_scaled(
2099 dim_dst.Height / frame_count
2101 // Create cropped and scaled crack images
2102 video::IImage *crack_cropped = driver->createImage(
2103 video::ECF_A8R8G8B8, dim_crack_cropped);
2104 video::IImage *crack_scaled = driver->createImage(
2105 video::ECF_A8R8G8B8, dim_crack_scaled);
2107 if (crack_cropped && crack_scaled)
2110 v2s32 pos_crack(0, progression*dim_crack.Width);
2111 crack->copyTo(crack_cropped,
2113 core::rect<s32>(pos_crack, dim_crack_cropped));
2114 // Scale crack image by copying
2115 crack_cropped->copyToScaling(crack_scaled);
2116 // Copy or overlay crack image onto each frame
2117 for (s32 i = 0; i < frame_count; ++i)
2119 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2122 blit_with_alpha_overlay(crack_scaled, dst,
2123 v2s32(0,0), dst_pos,
2128 blit_with_alpha(crack_scaled, dst,
2129 v2s32(0,0), dst_pos,
2136 crack_scaled->drop();
2139 crack_cropped->drop();
2142 void brighten(video::IImage *image)
2147 core::dimension2d<u32> dim = image->getDimension();
2149 for (u32 y=0; y<dim.Height; y++)
2150 for (u32 x=0; x<dim.Width; x++)
2152 video::SColor c = image->getPixel(x,y);
2153 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2154 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2155 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2156 image->setPixel(x,y,c);
2160 u32 parseImageTransform(const std::string& s)
2162 int total_transform = 0;
2164 std::string transform_names[8];
2165 transform_names[0] = "i";
2166 transform_names[1] = "r90";
2167 transform_names[2] = "r180";
2168 transform_names[3] = "r270";
2169 transform_names[4] = "fx";
2170 transform_names[6] = "fy";
2172 std::size_t pos = 0;
2173 while(pos < s.size())
2176 for (int i = 0; i <= 7; ++i)
2178 const std::string &name_i = transform_names[i];
2180 if (s[pos] == ('0' + i))
2186 else if (!(name_i.empty()) &&
2187 lowercase(s.substr(pos, name_i.size())) == name_i)
2190 pos += name_i.size();
2197 // Multiply total_transform and transform in the group D4
2200 new_total = (transform + total_transform) % 4;
2202 new_total = (transform - total_transform + 8) % 4;
2203 if ((transform >= 4) ^ (total_transform >= 4))
2206 total_transform = new_total;
2208 return total_transform;
2211 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2213 if (transform % 2 == 0)
2216 return core::dimension2d<u32>(dim.Height, dim.Width);
2219 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2221 if (src == NULL || dst == NULL)
2224 core::dimension2d<u32> dstdim = dst->getDimension();
2227 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2228 assert(transform <= 7);
2231 Compute the transformation from source coordinates (sx,sy)
2232 to destination coordinates (dx,dy).
2236 if (transform == 0) // identity
2237 sxn = 0, syn = 2; // sx = dx, sy = dy
2238 else if (transform == 1) // rotate by 90 degrees ccw
2239 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2240 else if (transform == 2) // rotate by 180 degrees
2241 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2242 else if (transform == 3) // rotate by 270 degrees ccw
2243 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2244 else if (transform == 4) // flip x
2245 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2246 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2247 sxn = 2, syn = 0; // sx = dy, sy = dx
2248 else if (transform == 6) // flip y
2249 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2250 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2251 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2253 for (u32 dy=0; dy<dstdim.Height; dy++)
2254 for (u32 dx=0; dx<dstdim.Width; dx++)
2256 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2257 u32 sx = entries[sxn];
2258 u32 sy = entries[syn];
2259 video::SColor c = src->getPixel(sx,sy);
2260 dst->setPixel(dx,dy,c);
2264 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2266 if (isKnownSourceImage("override_normal.png"))
2267 return getTexture("override_normal.png");
2268 std::string fname_base = name;
2269 std::string normal_ext = "_normal.png";
2270 size_t pos = fname_base.find(".");
2271 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2272 if (isKnownSourceImage(fname_normal)) {
2273 // look for image extension and replace it
2275 while ((i = fname_base.find(".", i)) != std::string::npos) {
2276 fname_base.replace(i, 4, normal_ext);
2277 i += normal_ext.length();
2279 return getTexture(fname_base);
2284 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2286 video::IVideoDriver *driver = m_device->getVideoDriver();
2287 video::SColor c(0, 0, 0, 0);
2288 video::ITexture *texture = getTexture(name);
2289 video::IImage *image = driver->createImage(texture,
2290 core::position2d<s32>(0, 0),
2291 texture->getOriginalSize());
2296 core::dimension2d<u32> dim = image->getDimension();
2299 step = dim.Width / 16;
2300 for (u16 x = 0; x < dim.Width; x += step) {
2301 for (u16 y = 0; y < dim.Width; y += step) {
2302 c = image->getPixel(x,y);
2303 if (c.getAlpha() > 0) {
2313 c.setRed(tR / total);
2314 c.setGreen(tG / total);
2315 c.setBlue(tB / total);
2322 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2324 std::string tname = "__shaderFlagsTexture";
2325 tname += normalmap_present ? "1" : "0";
2327 if (isKnownSourceImage(tname)) {
2328 return getTexture(tname);
2330 video::IVideoDriver *driver = m_device->getVideoDriver();
2331 video::IImage *flags_image = driver->createImage(
2332 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2333 sanity_check(flags_image != NULL);
2334 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2335 flags_image->setPixel(0, 0, c);
2336 insertSourceImage(tname, flags_image);
2337 flags_image->drop();
2338 return getTexture(tname);