3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
34 #include "util/strfnd.h"
35 #include "util/string.h" // for parseColorString()
36 #include "imagefilters.h"
37 #include "guiscalingfilter.h"
46 A cache from texture name to texture path
48 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
51 Replaces the filename extension.
53 std::string image = "a/image.png"
54 replace_ext(image, "jpg")
55 -> image = "a/image.jpg"
56 Returns true on success.
58 static bool replace_ext(std::string &path, const char *ext)
62 // Find place of last dot, fail if \ or / found.
64 for (s32 i=path.size()-1; i>=0; i--)
72 if (path[i] == '\\' || path[i] == '/')
75 // If not found, return an empty string
78 // Else make the new path
79 path = path.substr(0, last_dot_i+1) + ext;
84 Find out the full path of an image by trying different filename
89 std::string getImagePath(std::string path)
91 // A NULL-ended list of possible image extensions
92 const char *extensions[] = {
93 "png", "jpg", "bmp", "tga",
94 "pcx", "ppm", "psd", "wal", "rgb",
97 // If there is no extension, add one
98 if (removeStringEnd(path, extensions) == "")
100 // Check paths until something is found to exist
101 const char **ext = extensions;
103 bool r = replace_ext(path, *ext);
106 if (fs::PathExists(path))
109 while((++ext) != NULL);
115 Gets the path to a texture by first checking if the texture exists
116 in texture_path and if not, using the data path.
118 Checks all supported extensions by replacing the original extension.
120 If not found, returns "".
122 Utilizes a thread-safe cache.
124 std::string getTexturePath(const std::string &filename)
126 std::string fullpath = "";
130 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
135 Check from texture_path
137 std::string texture_path = g_settings->get("texture_path");
138 if (texture_path != "")
140 std::string testpath = texture_path + DIR_DELIM + filename;
141 // Check all filename extensions. Returns "" if not found.
142 fullpath = getImagePath(testpath);
146 Check from default data directory
150 std::string base_path = porting::path_share + DIR_DELIM + "textures"
151 + DIR_DELIM + "base" + DIR_DELIM + "pack";
152 std::string testpath = base_path + DIR_DELIM + filename;
153 // Check all filename extensions. Returns "" if not found.
154 fullpath = getImagePath(testpath);
157 // Add to cache (also an empty result is cached)
158 g_texturename_to_path_cache.set(filename, fullpath);
164 void clearTextureNameCache()
166 g_texturename_to_path_cache.clear();
170 Stores internal information about a texture.
176 video::ITexture *texture;
179 const std::string &name_,
180 video::ITexture *texture_=NULL
189 SourceImageCache: A cache used for storing source images.
192 class SourceImageCache
195 ~SourceImageCache() {
196 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
197 iter != m_images.end(); ++iter) {
198 iter->second->drop();
202 void insert(const std::string &name, video::IImage *img,
203 bool prefer_local, video::IVideoDriver *driver)
205 assert(img); // Pre-condition
207 std::map<std::string, video::IImage*>::iterator n;
208 n = m_images.find(name);
209 if (n != m_images.end()){
214 video::IImage* toadd = img;
215 bool need_to_grab = true;
217 // Try to use local texture instead if asked to
219 std::string path = getTexturePath(name);
221 video::IImage *img2 = driver->createImageFromFile(path.c_str());
224 need_to_grab = false;
231 m_images[name] = toadd;
233 video::IImage* get(const std::string &name)
235 std::map<std::string, video::IImage*>::iterator n;
236 n = m_images.find(name);
237 if (n != m_images.end())
241 // Primarily fetches from cache, secondarily tries to read from filesystem
242 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
244 std::map<std::string, video::IImage*>::iterator n;
245 n = m_images.find(name);
246 if (n != m_images.end()){
247 n->second->grab(); // Grab for caller
250 video::IVideoDriver* driver = device->getVideoDriver();
251 std::string path = getTexturePath(name);
253 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
254 <<name<<"\""<<std::endl;
257 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
259 video::IImage *img = driver->createImageFromFile(path.c_str());
262 m_images[name] = img;
263 img->grab(); // Grab for caller
268 std::map<std::string, video::IImage*> m_images;
275 class TextureSource : public IWritableTextureSource
278 TextureSource(IrrlichtDevice *device);
279 virtual ~TextureSource();
283 Now, assume a texture with the id 1 exists, and has the name
284 "stone.png^mineral1".
285 Then a random thread calls getTextureId for a texture called
286 "stone.png^mineral1^crack0".
287 ...Now, WTF should happen? Well:
288 - getTextureId strips off stuff recursively from the end until
289 the remaining part is found, or nothing is left when
290 something is stripped out
292 But it is slow to search for textures by names and modify them
294 - ContentFeatures is made to contain ids for the basic plain
296 - Crack textures can be slow by themselves, but the framework
300 - Assume a texture with the id 1 exists, and has the name
301 "stone.png^mineral_coal.png".
302 - Now getNodeTile() stumbles upon a node which uses
303 texture id 1, and determines that MATERIAL_FLAG_CRACK
304 must be applied to the tile
305 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
306 has received the current crack level 0 from the client. It
307 finds out the name of the texture with getTextureName(1),
308 appends "^crack0" to it and gets a new texture id with
309 getTextureId("stone.png^mineral_coal.png^crack0").
314 Gets a texture id from cache or
315 - if main thread, generates the texture, adds to cache and returns id.
316 - if other thread, adds to request queue and waits for main thread.
318 The id 0 points to a NULL texture. It is returned in case of error.
320 u32 getTextureId(const std::string &name);
322 // Finds out the name of a cached texture.
323 std::string getTextureName(u32 id);
326 If texture specified by the name pointed by the id doesn't
327 exist, create it, then return the cached texture.
329 Can be called from any thread. If called from some other thread
330 and not found in cache, the call is queued to the main thread
333 video::ITexture* getTexture(u32 id);
335 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
338 Get a texture specifically intended for mesh
339 application, i.e. not HUD, compositing, or other 2D
340 use. This texture may be a different size and may
341 have had additional filters applied.
343 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
345 // Returns a pointer to the irrlicht device
346 virtual IrrlichtDevice* getDevice()
351 bool isKnownSourceImage(const std::string &name)
353 bool is_known = false;
354 bool cache_found = m_source_image_existence.get(name, &is_known);
357 // Not found in cache; find out if a local file exists
358 is_known = (getTexturePath(name) != "");
359 m_source_image_existence.set(name, is_known);
363 // Processes queued texture requests from other threads.
364 // Shall be called from the main thread.
367 // Insert an image into the cache without touching the filesystem.
368 // Shall be called from the main thread.
369 void insertSourceImage(const std::string &name, video::IImage *img);
371 // Rebuild images and textures from the current set of source images
372 // Shall be called from the main thread.
373 void rebuildImagesAndTextures();
375 // Render a mesh to a texture.
376 // Returns NULL if render-to-texture failed.
377 // Shall be called from the main thread.
378 video::ITexture* generateTextureFromMesh(
379 const TextureFromMeshParams ¶ms);
381 // Generates an image from a full string like
382 // "stone.png^mineral_coal.png^[crack:1:0".
383 // Shall be called from the main thread.
384 video::IImage* generateImage(const std::string &name);
386 video::ITexture* getNormalTexture(const std::string &name);
387 video::SColor getTextureAverageColor(const std::string &name);
388 video::ITexture *getShaderFlagsTexture(bool normamap_present);
392 // The id of the thread that is allowed to use irrlicht directly
393 threadid_t m_main_thread;
394 // The irrlicht device
395 IrrlichtDevice *m_device;
397 // Cache of source images
398 // This should be only accessed from the main thread
399 SourceImageCache m_sourcecache;
401 // Generate a texture
402 u32 generateTexture(const std::string &name);
404 // Generate image based on a string like "stone.png" or "[crack:1:0".
405 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
406 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
408 // Thread-safe cache of what source images are known (true = known)
409 MutexedMap<std::string, bool> m_source_image_existence;
411 // A texture id is index in this array.
412 // The first position contains a NULL texture.
413 std::vector<TextureInfo> m_textureinfo_cache;
414 // Maps a texture name to an index in the former.
415 std::map<std::string, u32> m_name_to_id;
416 // The two former containers are behind this mutex
417 Mutex m_textureinfo_cache_mutex;
419 // Queued texture fetches (to be processed by the main thread)
420 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
422 // Textures that have been overwritten with other ones
423 // but can't be deleted because the ITexture* might still be used
424 std::vector<video::ITexture*> m_texture_trash;
426 // Cached settings needed for making textures from meshes
427 bool m_setting_trilinear_filter;
428 bool m_setting_bilinear_filter;
429 bool m_setting_anisotropic_filter;
432 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
434 return new TextureSource(device);
437 TextureSource::TextureSource(IrrlichtDevice *device):
440 assert(m_device); // Pre-condition
442 m_main_thread = thr_get_current_thread_id();
444 // Add a NULL TextureInfo as the first index, named ""
445 m_textureinfo_cache.push_back(TextureInfo(""));
446 m_name_to_id[""] = 0;
448 // Cache some settings
449 // Note: Since this is only done once, the game must be restarted
450 // for these settings to take effect
451 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
452 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
453 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
456 TextureSource::~TextureSource()
458 video::IVideoDriver* driver = m_device->getVideoDriver();
460 unsigned int textures_before = driver->getTextureCount();
462 for (std::vector<TextureInfo>::iterator iter =
463 m_textureinfo_cache.begin();
464 iter != m_textureinfo_cache.end(); ++iter)
468 driver->removeTexture(iter->texture);
470 m_textureinfo_cache.clear();
472 for (std::vector<video::ITexture*>::iterator iter =
473 m_texture_trash.begin(); iter != m_texture_trash.end();
475 video::ITexture *t = *iter;
477 //cleanup trashed texture
478 driver->removeTexture(t);
481 infostream << "~TextureSource() "<< textures_before << "/"
482 << driver->getTextureCount() << std::endl;
485 u32 TextureSource::getTextureId(const std::string &name)
487 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
491 See if texture already exists
493 MutexAutoLock lock(m_textureinfo_cache_mutex);
494 std::map<std::string, u32>::iterator n;
495 n = m_name_to_id.find(name);
496 if (n != m_name_to_id.end())
505 if (thr_is_current_thread(m_main_thread))
507 return generateTexture(name);
511 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
513 // We're gonna ask the result to be put into here
514 static ResultQueue<std::string, u32, u8, u8> result_queue;
516 // Throw a request in
517 m_get_texture_queue.add(name, 0, 0, &result_queue);
519 /*infostream<<"Waiting for texture from main thread, name=\""
520 <<name<<"\""<<std::endl;*/
525 // Wait result for a second
526 GetResult<std::string, u32, u8, u8>
527 result = result_queue.pop_front(1000);
529 if (result.key == name) {
534 catch(ItemNotFoundException &e)
536 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
541 infostream<<"getTextureId(): Failed"<<std::endl;
546 // Draw an image on top of an another one, using the alpha channel of the
548 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
549 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
551 // Like blit_with_alpha, but only modifies destination pixels that
553 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
554 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
556 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
557 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
558 // color alpha with the destination alpha.
559 // Otherwise, any pixels that are not fully transparent get the color alpha.
560 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
561 video::SColor color, int ratio, bool keep_alpha);
563 // Apply a mask to an image
564 static void apply_mask(video::IImage *mask, video::IImage *dst,
565 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
567 // Draw or overlay a crack
568 static void draw_crack(video::IImage *crack, video::IImage *dst,
569 bool use_overlay, s32 frame_count, s32 progression,
570 video::IVideoDriver *driver);
573 void brighten(video::IImage *image);
574 // Parse a transform name
575 u32 parseImageTransform(const std::string& s);
576 // Apply transform to image dimension
577 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
578 // Apply transform to image data
579 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
582 This method generates all the textures
584 u32 TextureSource::generateTexture(const std::string &name)
586 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
588 // Empty name means texture 0
590 infostream<<"generateTexture(): name is empty"<<std::endl;
596 See if texture already exists
598 MutexAutoLock lock(m_textureinfo_cache_mutex);
599 std::map<std::string, u32>::iterator n;
600 n = m_name_to_id.find(name);
601 if (n != m_name_to_id.end()) {
607 Calling only allowed from main thread
609 if (!thr_is_current_thread(m_main_thread)) {
610 errorstream<<"TextureSource::generateTexture() "
611 "called not from main thread"<<std::endl;
615 video::IVideoDriver *driver = m_device->getVideoDriver();
616 sanity_check(driver);
618 video::IImage *img = generateImage(name);
620 video::ITexture *tex = NULL;
624 img = Align2Npot2(img, driver);
626 // Create texture from resulting image
627 tex = driver->addTexture(name.c_str(), img);
628 guiScalingCache(io::path(name.c_str()), driver, img);
633 Add texture to caches (add NULL textures too)
636 MutexAutoLock lock(m_textureinfo_cache_mutex);
638 u32 id = m_textureinfo_cache.size();
639 TextureInfo ti(name, tex);
640 m_textureinfo_cache.push_back(ti);
641 m_name_to_id[name] = id;
646 std::string TextureSource::getTextureName(u32 id)
648 MutexAutoLock lock(m_textureinfo_cache_mutex);
650 if (id >= m_textureinfo_cache.size())
652 errorstream<<"TextureSource::getTextureName(): id="<<id
653 <<" >= m_textureinfo_cache.size()="
654 <<m_textureinfo_cache.size()<<std::endl;
658 return m_textureinfo_cache[id].name;
661 video::ITexture* TextureSource::getTexture(u32 id)
663 MutexAutoLock lock(m_textureinfo_cache_mutex);
665 if (id >= m_textureinfo_cache.size())
668 return m_textureinfo_cache[id].texture;
671 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
673 u32 actual_id = getTextureId(name);
677 return getTexture(actual_id);
680 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
682 return getTexture(name + "^[applyfiltersformesh", id);
685 void TextureSource::processQueue()
690 //NOTE this is only thread safe for ONE consumer thread!
691 if (!m_get_texture_queue.empty())
693 GetRequest<std::string, u32, u8, u8>
694 request = m_get_texture_queue.pop();
696 /*infostream<<"TextureSource::processQueue(): "
697 <<"got texture request with "
698 <<"name=\""<<request.key<<"\""
701 m_get_texture_queue.pushResult(request, generateTexture(request.key));
705 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
707 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
709 sanity_check(thr_is_current_thread(m_main_thread));
711 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
712 m_source_image_existence.set(name, true);
715 void TextureSource::rebuildImagesAndTextures()
717 MutexAutoLock lock(m_textureinfo_cache_mutex);
719 video::IVideoDriver* driver = m_device->getVideoDriver();
720 sanity_check(driver);
723 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
724 TextureInfo *ti = &m_textureinfo_cache[i];
725 video::IImage *img = generateImage(ti->name);
727 img = Align2Npot2(img, driver);
728 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
729 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
731 // Create texture from resulting image
732 video::ITexture *t = NULL;
734 t = driver->addTexture(ti->name.c_str(), img);
735 guiScalingCache(io::path(ti->name.c_str()), driver, img);
738 video::ITexture *t_old = ti->texture;
743 m_texture_trash.push_back(t_old);
747 video::ITexture* TextureSource::generateTextureFromMesh(
748 const TextureFromMeshParams ¶ms)
750 video::IVideoDriver *driver = m_device->getVideoDriver();
751 sanity_check(driver);
754 const GLubyte* renderstr = glGetString(GL_RENDERER);
755 std::string renderer((char*) renderstr);
757 // use no render to texture hack
759 (renderer.find("Adreno") != std::string::npos) ||
760 (renderer.find("Mali") != std::string::npos) ||
761 (renderer.find("Immersion") != std::string::npos) ||
762 (renderer.find("Tegra") != std::string::npos) ||
763 g_settings->getBool("inventory_image_hack")
765 // Get a scene manager
766 scene::ISceneManager *smgr_main = m_device->getSceneManager();
767 sanity_check(smgr_main);
768 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
771 const float scaling = 0.2;
773 scene::IMeshSceneNode* meshnode =
774 smgr->addMeshSceneNode(params.mesh, NULL,
775 -1, v3f(0,0,0), v3f(0,0,0),
776 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
777 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
778 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
779 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
780 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
781 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
783 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
784 params.camera_position, params.camera_lookat);
785 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
786 camera->setProjectionMatrix(params.camera_projection_matrix, false);
788 smgr->setAmbientLight(params.ambient_light);
789 smgr->addLightSceneNode(0,
790 params.light_position,
792 params.light_radius*scaling);
794 core::dimension2d<u32> screen = driver->getScreenSize();
797 driver->beginScene(true, true, video::SColor(0,0,0,0));
798 driver->clearZBuffer();
801 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
803 irr::video::IImage* rawImage =
804 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
806 u8* pixels = static_cast<u8*>(rawImage->lock());
813 core::rect<s32> source(
814 screen.Width /2 - (screen.Width * (scaling / 2)),
815 screen.Height/2 - (screen.Height * (scaling / 2)),
816 screen.Width /2 + (screen.Width * (scaling / 2)),
817 screen.Height/2 + (screen.Height * (scaling / 2))
820 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
821 partsize.Width, partsize.Height, GL_RGBA,
822 GL_UNSIGNED_BYTE, pixels);
826 // Drop scene manager
829 unsigned int pixelcount = partsize.Width*partsize.Height;
832 for (unsigned int i=0; i < pixelcount; i++) {
850 video::IImage* inventory_image =
851 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
853 rawImage->copyToScaling(inventory_image);
856 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
858 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
859 inventory_image->drop();
862 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
866 driver->makeColorKeyTexture(rtt, v2s32(0,0));
868 if (params.delete_texture_on_shutdown)
869 m_texture_trash.push_back(rtt);
875 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
877 static bool warned = false;
880 errorstream<<"TextureSource::generateTextureFromMesh(): "
881 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
887 // Create render target texture
888 video::ITexture *rtt = driver->addRenderTargetTexture(
889 params.dim, params.rtt_texture_name.c_str(),
890 video::ECF_A8R8G8B8);
893 errorstream<<"TextureSource::generateTextureFromMesh(): "
894 <<"addRenderTargetTexture returned NULL."<<std::endl;
899 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
900 driver->removeTexture(rtt);
901 errorstream<<"TextureSource::generateTextureFromMesh(): "
902 <<"failed to set render target"<<std::endl;
906 // Get a scene manager
907 scene::ISceneManager *smgr_main = m_device->getSceneManager();
909 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
912 scene::IMeshSceneNode* meshnode =
913 smgr->addMeshSceneNode(params.mesh, NULL,
914 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
915 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
916 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
917 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
918 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
919 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
921 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
922 params.camera_position, params.camera_lookat);
923 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
924 camera->setProjectionMatrix(params.camera_projection_matrix, false);
926 smgr->setAmbientLight(params.ambient_light);
927 smgr->addLightSceneNode(0,
928 params.light_position,
930 params.light_radius);
933 driver->beginScene(true, true, video::SColor(0,0,0,0));
937 // Drop scene manager
940 // Unset render target
941 driver->setRenderTarget(0, false, true, 0);
943 if (params.delete_texture_on_shutdown)
944 m_texture_trash.push_back(rtt);
949 video::IImage* TextureSource::generateImage(const std::string &name)
955 const char separator = '^';
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--) {
965 if (paren_bal == 0) {
966 last_separator_pos = i;
967 i = -1; // break out of loop
971 if (paren_bal == 0) {
972 errorstream << "generateImage(): unbalanced parentheses"
973 << "(extranous '(') while generating texture \""
974 << name << "\"" << std::endl;
987 errorstream << "generateImage(): unbalanced parentheses"
988 << "(missing matching '(') while generating texture \""
989 << name << "\"" << std::endl;
994 video::IImage *baseimg = NULL;
997 If separator was found, make the base image
998 using a recursive call.
1000 if (last_separator_pos != -1) {
1001 baseimg = generateImage(name.substr(0, last_separator_pos));
1005 video::IVideoDriver* driver = m_device->getVideoDriver();
1006 sanity_check(driver);
1009 Parse out the last part of the name of the image and act
1013 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1016 If this name is enclosed in parentheses, generate it
1017 and blit it onto the base image
1019 if (last_part_of_name[0] == paren_open
1020 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1021 std::string name2 = last_part_of_name.substr(1,
1022 last_part_of_name.size() - 2);
1023 video::IImage *tmp = generateImage(name2);
1025 errorstream << "generateImage(): "
1026 "Failed to generate \"" << name2 << "\""
1030 core::dimension2d<u32> dim = tmp->getDimension();
1032 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1033 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1035 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1036 // Generate image according to part of name
1037 errorstream << "generateImage(): "
1038 "Failed to generate \"" << last_part_of_name << "\""
1042 // If no resulting image, print a warning
1043 if (baseimg == NULL) {
1044 errorstream << "generateImage(): baseimg is NULL (attempted to"
1045 " create texture \"" << name << "\")" << std::endl;
1052 #include <GLES/gl.h>
1054 * Check and align image to npot2 if required by hardware
1055 * @param image image to check for npot2 alignment
1056 * @param driver driver to use for image operations
1057 * @return image or copy of image aligned to npot2
1059 video::IImage * Align2Npot2(video::IImage * image,
1060 video::IVideoDriver* driver)
1062 if (image == NULL) {
1066 core::dimension2d<u32> dim = image->getDimension();
1068 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1069 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1073 unsigned int height = npot2(dim.Height);
1074 unsigned int width = npot2(dim.Width);
1076 if ((dim.Height == height) &&
1077 (dim.Width == width)) {
1081 if (dim.Height > height) {
1085 if (dim.Width > width) {
1089 video::IImage *targetimage =
1090 driver->createImage(video::ECF_A8R8G8B8,
1091 core::dimension2d<u32>(width, height));
1093 if (targetimage != NULL) {
1094 image->copyToScaling(targetimage);
1102 bool TextureSource::generateImagePart(std::string part_of_name,
1103 video::IImage *& baseimg)
1105 video::IVideoDriver* driver = m_device->getVideoDriver();
1106 sanity_check(driver);
1108 // Stuff starting with [ are special commands
1109 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1111 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1113 image = Align2Npot2(image, driver);
1115 if (image == NULL) {
1116 if (part_of_name != "") {
1117 if (part_of_name.find("_normal.png") == std::string::npos){
1118 errorstream<<"generateImage(): Could not load image \""
1119 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1120 errorstream<<"generateImage(): Creating a dummy"
1121 <<" image for \""<<part_of_name<<"\""<<std::endl;
1123 infostream<<"generateImage(): Could not load normal map \""
1124 <<part_of_name<<"\""<<std::endl;
1125 infostream<<"generateImage(): Creating a dummy"
1126 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1130 // Just create a dummy image
1131 //core::dimension2d<u32> dim(2,2);
1132 core::dimension2d<u32> dim(1,1);
1133 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1134 sanity_check(image != NULL);
1135 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1136 image->setPixel(1,0, video::SColor(255,0,255,0));
1137 image->setPixel(0,1, video::SColor(255,0,0,255));
1138 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1139 image->setPixel(0,0, video::SColor(255,myrand()%256,
1140 myrand()%256,myrand()%256));
1141 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1142 myrand()%256,myrand()%256));
1143 image->setPixel(0,1, video::SColor(255,myrand()%256,
1144 myrand()%256,myrand()%256));
1145 image->setPixel(1,1, video::SColor(255,myrand()%256,
1146 myrand()%256,myrand()%256));*/
1149 // If base image is NULL, load as base.
1150 if (baseimg == NULL)
1152 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1154 Copy it this way to get an alpha channel.
1155 Otherwise images with alpha cannot be blitted on
1156 images that don't have alpha in the original file.
1158 core::dimension2d<u32> dim = image->getDimension();
1159 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1160 image->copyTo(baseimg);
1162 // Else blit on base.
1165 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1166 // Size of the copied area
1167 core::dimension2d<u32> dim = image->getDimension();
1168 //core::dimension2d<u32> dim(16,16);
1169 // Position to copy the blitted to in the base image
1170 core::position2d<s32> pos_to(0,0);
1171 // Position to copy the blitted from in the blitted image
1172 core::position2d<s32> pos_from(0,0);
1174 /*image->copyToWithAlpha(baseimg, pos_to,
1175 core::rect<s32>(pos_from, dim),
1176 video::SColor(255,255,255,255),
1179 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1180 if (dim == dim_dst) {
1181 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1182 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1183 // Upscale overlying image
1184 video::IImage* scaled_image = m_device->getVideoDriver()->
1185 createImage(video::ECF_A8R8G8B8, dim_dst);
1186 image->copyToScaling(scaled_image);
1188 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1189 scaled_image->drop();
1191 // Upscale base image
1192 video::IImage* scaled_base = m_device->getVideoDriver()->
1193 createImage(video::ECF_A8R8G8B8, dim);
1194 baseimg->copyToScaling(scaled_base);
1196 baseimg = scaled_base;
1198 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1206 // A special texture modification
1208 /*infostream<<"generateImage(): generating special "
1209 <<"modification \""<<part_of_name<<"\""
1215 Adds a cracking texture
1216 N = animation frame count, P = crack progression
1218 if (str_starts_with(part_of_name, "[crack"))
1220 if (baseimg == NULL) {
1221 errorstream<<"generateImagePart(): baseimg == NULL "
1222 <<"for part_of_name=\""<<part_of_name
1223 <<"\", cancelling."<<std::endl;
1227 // Crack image number and overlay option
1228 bool use_overlay = (part_of_name[6] == 'o');
1229 Strfnd sf(part_of_name);
1231 s32 frame_count = stoi(sf.next(":"));
1232 s32 progression = stoi(sf.next(":"));
1234 if (progression >= 0) {
1238 It is an image with a number of cracking stages
1241 video::IImage *img_crack = m_sourcecache.getOrLoad(
1242 "crack_anylength.png", m_device);
1245 draw_crack(img_crack, baseimg,
1246 use_overlay, frame_count,
1247 progression, driver);
1253 [combine:WxH:X,Y=filename:X,Y=filename2
1254 Creates a bigger texture from an amount of smaller ones
1256 else if (str_starts_with(part_of_name, "[combine"))
1258 Strfnd sf(part_of_name);
1260 u32 w0 = stoi(sf.next("x"));
1261 u32 h0 = stoi(sf.next(":"));
1262 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1263 core::dimension2d<u32> dim(w0,h0);
1264 if (baseimg == NULL) {
1265 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1266 baseimg->fill(video::SColor(0,0,0,0));
1268 while (sf.at_end() == false) {
1269 u32 x = stoi(sf.next(","));
1270 u32 y = stoi(sf.next("="));
1271 std::string filename = sf.next(":");
1272 infostream<<"Adding \""<<filename
1273 <<"\" to combined ("<<x<<","<<y<<")"
1275 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1277 core::dimension2d<u32> dim = img->getDimension();
1278 infostream<<"Size "<<dim.Width
1279 <<"x"<<dim.Height<<std::endl;
1280 core::position2d<s32> pos_base(x, y);
1281 video::IImage *img2 =
1282 driver->createImage(video::ECF_A8R8G8B8, dim);
1285 /*img2->copyToWithAlpha(baseimg, pos_base,
1286 core::rect<s32>(v2s32(0,0), dim),
1287 video::SColor(255,255,255,255),
1289 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1292 errorstream << "generateImagePart(): Failed to load image \""
1293 << filename << "\" for [combine" << std::endl;
1300 else if (str_starts_with(part_of_name, "[brighten"))
1302 if (baseimg == NULL) {
1303 errorstream<<"generateImagePart(): baseimg==NULL "
1304 <<"for part_of_name=\""<<part_of_name
1305 <<"\", cancelling."<<std::endl;
1313 Make image completely opaque.
1314 Used for the leaves texture when in old leaves mode, so
1315 that the transparent parts don't look completely black
1316 when simple alpha channel is used for rendering.
1318 else if (str_starts_with(part_of_name, "[noalpha"))
1320 if (baseimg == NULL){
1321 errorstream<<"generateImagePart(): baseimg==NULL "
1322 <<"for part_of_name=\""<<part_of_name
1323 <<"\", cancelling."<<std::endl;
1327 core::dimension2d<u32> dim = baseimg->getDimension();
1329 // Set alpha to full
1330 for (u32 y=0; y<dim.Height; y++)
1331 for (u32 x=0; x<dim.Width; x++)
1333 video::SColor c = baseimg->getPixel(x,y);
1335 baseimg->setPixel(x,y,c);
1340 Convert one color to transparent.
1342 else if (str_starts_with(part_of_name, "[makealpha:"))
1344 if (baseimg == NULL) {
1345 errorstream<<"generateImagePart(): baseimg == NULL "
1346 <<"for part_of_name=\""<<part_of_name
1347 <<"\", cancelling."<<std::endl;
1351 Strfnd sf(part_of_name.substr(11));
1352 u32 r1 = stoi(sf.next(","));
1353 u32 g1 = stoi(sf.next(","));
1354 u32 b1 = stoi(sf.next(""));
1356 core::dimension2d<u32> dim = baseimg->getDimension();
1358 /*video::IImage *oldbaseimg = baseimg;
1359 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1360 oldbaseimg->copyTo(baseimg);
1361 oldbaseimg->drop();*/
1363 // Set alpha to full
1364 for (u32 y=0; y<dim.Height; y++)
1365 for (u32 x=0; x<dim.Width; x++)
1367 video::SColor c = baseimg->getPixel(x,y);
1369 u32 g = c.getGreen();
1370 u32 b = c.getBlue();
1371 if (!(r == r1 && g == g1 && b == b1))
1374 baseimg->setPixel(x,y,c);
1379 Rotates and/or flips the image.
1381 N can be a number (between 0 and 7) or a transform name.
1382 Rotations are counter-clockwise.
1384 1 R90 rotate by 90 degrees
1385 2 R180 rotate by 180 degrees
1386 3 R270 rotate by 270 degrees
1388 5 FXR90 flip X then rotate by 90 degrees
1390 7 FYR90 flip Y then rotate by 90 degrees
1392 Note: Transform names can be concatenated to produce
1393 their product (applies the first then the second).
1394 The resulting transform will be equivalent to one of the
1395 eight existing ones, though (see: dihedral group).
1397 else if (str_starts_with(part_of_name, "[transform"))
1399 if (baseimg == NULL) {
1400 errorstream<<"generateImagePart(): baseimg == NULL "
1401 <<"for part_of_name=\""<<part_of_name
1402 <<"\", cancelling."<<std::endl;
1406 u32 transform = parseImageTransform(part_of_name.substr(10));
1407 core::dimension2d<u32> dim = imageTransformDimension(
1408 transform, baseimg->getDimension());
1409 video::IImage *image = driver->createImage(
1410 baseimg->getColorFormat(), dim);
1411 sanity_check(image != NULL);
1412 imageTransform(transform, baseimg, image);
1417 [inventorycube{topimage{leftimage{rightimage
1418 In every subimage, replace ^ with &.
1419 Create an "inventory cube".
1420 NOTE: This should be used only on its own.
1421 Example (a grass block (not actually used in game):
1422 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1424 else if (str_starts_with(part_of_name, "[inventorycube"))
1426 if (baseimg != NULL){
1427 errorstream<<"generateImagePart(): baseimg != NULL "
1428 <<"for part_of_name=\""<<part_of_name
1429 <<"\", cancelling."<<std::endl;
1433 str_replace(part_of_name, '&', '^');
1434 Strfnd sf(part_of_name);
1436 std::string imagename_top = sf.next("{");
1437 std::string imagename_left = sf.next("{");
1438 std::string imagename_right = sf.next("{");
1440 // Generate images for the faces of the cube
1441 video::IImage *img_top = generateImage(imagename_top);
1442 video::IImage *img_left = generateImage(imagename_left);
1443 video::IImage *img_right = generateImage(imagename_right);
1445 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1446 errorstream << "generateImagePart(): Failed to create textures"
1447 << " for inventorycube \"" << part_of_name << "\""
1449 baseimg = generateImage(imagename_top);
1454 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1455 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1457 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1458 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1460 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1461 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1464 // Create textures from images
1465 video::ITexture *texture_top = driver->addTexture(
1466 (imagename_top + "__temp__").c_str(), img_top);
1467 video::ITexture *texture_left = driver->addTexture(
1468 (imagename_left + "__temp__").c_str(), img_left);
1469 video::ITexture *texture_right = driver->addTexture(
1470 (imagename_right + "__temp__").c_str(), img_right);
1471 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1479 Draw a cube mesh into a render target texture
1481 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1482 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1483 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1484 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1485 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1486 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1487 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1488 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1490 TextureFromMeshParams params;
1492 params.dim.set(64, 64);
1493 params.rtt_texture_name = part_of_name + "_RTT";
1494 // We will delete the rtt texture ourselves
1495 params.delete_texture_on_shutdown = false;
1496 params.camera_position.set(0, 1.0, -1.5);
1497 params.camera_position.rotateXZBy(45);
1498 params.camera_lookat.set(0, 0, 0);
1499 // Set orthogonal projection
1500 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1501 1.65, 1.65, 0, 100);
1503 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1504 params.light_position.set(10, 100, -50);
1505 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1506 params.light_radius = 1000;
1508 video::ITexture *rtt = generateTextureFromMesh(params);
1514 driver->removeTexture(texture_top);
1515 driver->removeTexture(texture_left);
1516 driver->removeTexture(texture_right);
1519 baseimg = generateImage(imagename_top);
1523 // Create image of render target
1524 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1525 FATAL_ERROR_IF(!image, "Could not create image of render target");
1528 driver->removeTexture(rtt);
1530 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1533 image->copyTo(baseimg);
1538 [lowpart:percent:filename
1539 Adds the lower part of a texture
1541 else if (str_starts_with(part_of_name, "[lowpart:"))
1543 Strfnd sf(part_of_name);
1545 u32 percent = stoi(sf.next(":"));
1546 std::string filename = sf.next(":");
1547 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1549 if (baseimg == NULL)
1550 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1551 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1554 core::dimension2d<u32> dim = img->getDimension();
1555 core::position2d<s32> pos_base(0, 0);
1556 video::IImage *img2 =
1557 driver->createImage(video::ECF_A8R8G8B8, dim);
1560 core::position2d<s32> clippos(0, 0);
1561 clippos.Y = dim.Height * (100-percent) / 100;
1562 core::dimension2d<u32> clipdim = dim;
1563 clipdim.Height = clipdim.Height * percent / 100 + 1;
1564 core::rect<s32> cliprect(clippos, clipdim);
1565 img2->copyToWithAlpha(baseimg, pos_base,
1566 core::rect<s32>(v2s32(0,0), dim),
1567 video::SColor(255,255,255,255),
1574 Crops a frame of a vertical animation.
1575 N = frame count, I = frame index
1577 else if (str_starts_with(part_of_name, "[verticalframe:"))
1579 Strfnd sf(part_of_name);
1581 u32 frame_count = stoi(sf.next(":"));
1582 u32 frame_index = stoi(sf.next(":"));
1584 if (baseimg == NULL){
1585 errorstream<<"generateImagePart(): baseimg != NULL "
1586 <<"for part_of_name=\""<<part_of_name
1587 <<"\", cancelling."<<std::endl;
1591 v2u32 frame_size = baseimg->getDimension();
1592 frame_size.Y /= frame_count;
1594 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1597 errorstream<<"generateImagePart(): Could not create image "
1598 <<"for part_of_name=\""<<part_of_name
1599 <<"\", cancelling."<<std::endl;
1603 // Fill target image with transparency
1604 img->fill(video::SColor(0,0,0,0));
1606 core::dimension2d<u32> dim = frame_size;
1607 core::position2d<s32> pos_dst(0, 0);
1608 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1609 baseimg->copyToWithAlpha(img, pos_dst,
1610 core::rect<s32>(pos_src, dim),
1611 video::SColor(255,255,255,255),
1619 Applies a mask to an image
1621 else if (str_starts_with(part_of_name, "[mask:"))
1623 if (baseimg == NULL) {
1624 errorstream << "generateImage(): baseimg == NULL "
1625 << "for part_of_name=\"" << part_of_name
1626 << "\", cancelling." << std::endl;
1629 Strfnd sf(part_of_name);
1631 std::string filename = sf.next(":");
1633 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1635 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1636 img->getDimension());
1639 errorstream << "generateImage(): Failed to load \""
1640 << filename << "\".";
1645 Overlays image with given color
1646 color = color as ColorString
1648 else if (str_starts_with(part_of_name, "[colorize:"))
1650 Strfnd sf(part_of_name);
1652 std::string color_str = sf.next(":");
1653 std::string ratio_str = sf.next(":");
1655 if (baseimg == NULL) {
1656 errorstream << "generateImagePart(): baseimg != NULL "
1657 << "for part_of_name=\"" << part_of_name
1658 << "\", cancelling." << std::endl;
1662 video::SColor color;
1664 bool keep_alpha = false;
1666 if (!parseColorString(color_str, color, false))
1669 if (is_number(ratio_str))
1670 ratio = mystoi(ratio_str, 0, 255);
1671 else if (ratio_str == "alpha")
1674 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1676 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1678 // Apply the "clean transparent" filter, if configured.
1679 if (g_settings->getBool("texture_clean_transparent"))
1680 imageCleanTransparent(baseimg, 127);
1682 /* Upscale textures to user's requested minimum size. This is a trick to make
1683 * filters look as good on low-res textures as on high-res ones, by making
1684 * low-res textures BECOME high-res ones. This is helpful for worlds that
1685 * mix high- and low-res textures, or for mods with least-common-denominator
1686 * textures that don't have the resources to offer high-res alternatives.
1688 s32 scaleto = g_settings->getS32("texture_min_size");
1690 const core::dimension2d<u32> dim = baseimg->getDimension();
1692 /* Calculate scaling needed to make the shortest texture dimension
1693 * equal to the target minimum. If e.g. this is a vertical frames
1694 * animation, the short dimension will be the real size.
1696 u32 xscale = scaleto / dim.Width;
1697 u32 yscale = scaleto / dim.Height;
1698 u32 scale = (xscale > yscale) ? xscale : yscale;
1700 // Never downscale; only scale up by 2x or more.
1702 u32 w = scale * dim.Width;
1703 u32 h = scale * dim.Height;
1704 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1705 video::IImage *newimg = driver->createImage(
1706 baseimg->getColorFormat(), newdim);
1707 baseimg->copyToScaling(newimg);
1715 Resizes the base image to the given dimensions
1717 else if (str_starts_with(part_of_name, "[resize"))
1719 if (baseimg == NULL) {
1720 errorstream << "generateImagePart(): baseimg == NULL "
1721 << "for part_of_name=\""<< part_of_name
1722 << "\", cancelling." << std::endl;
1726 Strfnd sf(part_of_name);
1728 u32 width = stoi(sf.next("x"));
1729 u32 height = stoi(sf.next(""));
1730 core::dimension2d<u32> dim(width, height);
1732 video::IImage* image = m_device->getVideoDriver()->
1733 createImage(video::ECF_A8R8G8B8, dim);
1734 baseimg->copyToScaling(image);
1740 errorstream << "generateImagePart(): Invalid "
1741 " modification: \"" << part_of_name << "\"" << std::endl;
1749 Draw an image on top of an another one, using the alpha channel of the
1752 This exists because IImage::copyToWithAlpha() doesn't seem to always
1755 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1756 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1758 for (u32 y0=0; y0<size.Y; y0++)
1759 for (u32 x0=0; x0<size.X; x0++)
1761 s32 src_x = src_pos.X + x0;
1762 s32 src_y = src_pos.Y + y0;
1763 s32 dst_x = dst_pos.X + x0;
1764 s32 dst_y = dst_pos.Y + y0;
1765 video::SColor src_c = src->getPixel(src_x, src_y);
1766 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1767 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1768 dst->setPixel(dst_x, dst_y, dst_c);
1773 Draw an image on top of an another one, using the alpha channel of the
1774 source image; only modify fully opaque pixels in destinaion
1776 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1777 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1779 for (u32 y0=0; y0<size.Y; y0++)
1780 for (u32 x0=0; x0<size.X; x0++)
1782 s32 src_x = src_pos.X + x0;
1783 s32 src_y = src_pos.Y + y0;
1784 s32 dst_x = dst_pos.X + x0;
1785 s32 dst_y = dst_pos.Y + y0;
1786 video::SColor src_c = src->getPixel(src_x, src_y);
1787 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1788 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1790 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1791 dst->setPixel(dst_x, dst_y, dst_c);
1796 // This function has been disabled because it is currently unused.
1797 // Feel free to re-enable if you find it handy.
1800 Draw an image on top of an another one, using the specified ratio
1801 modify all partially-opaque pixels in the destination.
1803 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1804 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1806 for (u32 y0 = 0; y0 < size.Y; y0++)
1807 for (u32 x0 = 0; x0 < size.X; x0++)
1809 s32 src_x = src_pos.X + x0;
1810 s32 src_y = src_pos.Y + y0;
1811 s32 dst_x = dst_pos.X + x0;
1812 s32 dst_y = dst_pos.Y + y0;
1813 video::SColor src_c = src->getPixel(src_x, src_y);
1814 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1815 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1818 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1820 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1821 dst->setPixel(dst_x, dst_y, dst_c);
1828 Apply color to destination
1830 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1831 video::SColor color, int ratio, bool keep_alpha)
1833 u32 alpha = color.getAlpha();
1834 video::SColor dst_c;
1835 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1836 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1838 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1839 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1840 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1841 if (dst_alpha > 0) {
1842 dst_c.setAlpha(dst_alpha * alpha / 255);
1843 dst->setPixel(x, y, dst_c);
1846 } else { // replace the color including the alpha
1847 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1848 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1849 if (dst->getPixel(x, y).getAlpha() > 0)
1850 dst->setPixel(x, y, color);
1852 } else { // interpolate between the color and destination
1853 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1854 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1855 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1856 dst_c = dst->getPixel(x, y);
1857 if (dst_c.getAlpha() > 0) {
1858 dst_c = color.getInterpolated(dst_c, interp);
1859 dst->setPixel(x, y, dst_c);
1866 Apply mask to destination
1868 static void apply_mask(video::IImage *mask, video::IImage *dst,
1869 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1871 for (u32 y0 = 0; y0 < size.Y; y0++) {
1872 for (u32 x0 = 0; x0 < size.X; x0++) {
1873 s32 mask_x = x0 + mask_pos.X;
1874 s32 mask_y = y0 + mask_pos.Y;
1875 s32 dst_x = x0 + dst_pos.X;
1876 s32 dst_y = y0 + dst_pos.Y;
1877 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1878 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1879 dst_c.color &= mask_c.color;
1880 dst->setPixel(dst_x, dst_y, dst_c);
1885 static void draw_crack(video::IImage *crack, video::IImage *dst,
1886 bool use_overlay, s32 frame_count, s32 progression,
1887 video::IVideoDriver *driver)
1889 // Dimension of destination image
1890 core::dimension2d<u32> dim_dst = dst->getDimension();
1891 // Dimension of original image
1892 core::dimension2d<u32> dim_crack = crack->getDimension();
1893 // Count of crack stages
1894 s32 crack_count = dim_crack.Height / dim_crack.Width;
1895 // Limit frame_count
1896 if (frame_count > (s32) dim_dst.Height)
1897 frame_count = dim_dst.Height;
1898 if (frame_count < 1)
1900 // Limit progression
1901 if (progression > crack_count-1)
1902 progression = crack_count-1;
1903 // Dimension of a single crack stage
1904 core::dimension2d<u32> dim_crack_cropped(
1908 // Dimension of the scaled crack stage,
1909 // which is the same as the dimension of a single destination frame
1910 core::dimension2d<u32> dim_crack_scaled(
1912 dim_dst.Height / frame_count
1914 // Create cropped and scaled crack images
1915 video::IImage *crack_cropped = driver->createImage(
1916 video::ECF_A8R8G8B8, dim_crack_cropped);
1917 video::IImage *crack_scaled = driver->createImage(
1918 video::ECF_A8R8G8B8, dim_crack_scaled);
1920 if (crack_cropped && crack_scaled)
1923 v2s32 pos_crack(0, progression*dim_crack.Width);
1924 crack->copyTo(crack_cropped,
1926 core::rect<s32>(pos_crack, dim_crack_cropped));
1927 // Scale crack image by copying
1928 crack_cropped->copyToScaling(crack_scaled);
1929 // Copy or overlay crack image onto each frame
1930 for (s32 i = 0; i < frame_count; ++i)
1932 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1935 blit_with_alpha_overlay(crack_scaled, dst,
1936 v2s32(0,0), dst_pos,
1941 blit_with_alpha(crack_scaled, dst,
1942 v2s32(0,0), dst_pos,
1949 crack_scaled->drop();
1952 crack_cropped->drop();
1955 void brighten(video::IImage *image)
1960 core::dimension2d<u32> dim = image->getDimension();
1962 for (u32 y=0; y<dim.Height; y++)
1963 for (u32 x=0; x<dim.Width; x++)
1965 video::SColor c = image->getPixel(x,y);
1966 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1967 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1968 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1969 image->setPixel(x,y,c);
1973 u32 parseImageTransform(const std::string& s)
1975 int total_transform = 0;
1977 std::string transform_names[8];
1978 transform_names[0] = "i";
1979 transform_names[1] = "r90";
1980 transform_names[2] = "r180";
1981 transform_names[3] = "r270";
1982 transform_names[4] = "fx";
1983 transform_names[6] = "fy";
1985 std::size_t pos = 0;
1986 while(pos < s.size())
1989 for (int i = 0; i <= 7; ++i)
1991 const std::string &name_i = transform_names[i];
1993 if (s[pos] == ('0' + i))
1999 else if (!(name_i.empty()) &&
2000 lowercase(s.substr(pos, name_i.size())) == name_i)
2003 pos += name_i.size();
2010 // Multiply total_transform and transform in the group D4
2013 new_total = (transform + total_transform) % 4;
2015 new_total = (transform - total_transform + 8) % 4;
2016 if ((transform >= 4) ^ (total_transform >= 4))
2019 total_transform = new_total;
2021 return total_transform;
2024 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2026 if (transform % 2 == 0)
2029 return core::dimension2d<u32>(dim.Height, dim.Width);
2032 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2034 if (src == NULL || dst == NULL)
2037 core::dimension2d<u32> dstdim = dst->getDimension();
2040 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2041 assert(transform <= 7);
2044 Compute the transformation from source coordinates (sx,sy)
2045 to destination coordinates (dx,dy).
2049 if (transform == 0) // identity
2050 sxn = 0, syn = 2; // sx = dx, sy = dy
2051 else if (transform == 1) // rotate by 90 degrees ccw
2052 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2053 else if (transform == 2) // rotate by 180 degrees
2054 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2055 else if (transform == 3) // rotate by 270 degrees ccw
2056 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2057 else if (transform == 4) // flip x
2058 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2059 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2060 sxn = 2, syn = 0; // sx = dy, sy = dx
2061 else if (transform == 6) // flip y
2062 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2063 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2064 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2066 for (u32 dy=0; dy<dstdim.Height; dy++)
2067 for (u32 dx=0; dx<dstdim.Width; dx++)
2069 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2070 u32 sx = entries[sxn];
2071 u32 sy = entries[syn];
2072 video::SColor c = src->getPixel(sx,sy);
2073 dst->setPixel(dx,dy,c);
2077 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2079 if (isKnownSourceImage("override_normal.png"))
2080 return getTexture("override_normal.png");
2081 std::string fname_base = name;
2082 std::string normal_ext = "_normal.png";
2083 size_t pos = fname_base.find(".");
2084 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2085 if (isKnownSourceImage(fname_normal)) {
2086 // look for image extension and replace it
2088 while ((i = fname_base.find(".", i)) != std::string::npos) {
2089 fname_base.replace(i, 4, normal_ext);
2090 i += normal_ext.length();
2092 return getTexture(fname_base);
2097 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2099 video::IVideoDriver *driver = m_device->getVideoDriver();
2100 video::SColor c(0, 0, 0, 0);
2101 video::ITexture *texture = getTexture(name);
2102 video::IImage *image = driver->createImage(texture,
2103 core::position2d<s32>(0, 0),
2104 texture->getOriginalSize());
2109 core::dimension2d<u32> dim = image->getDimension();
2112 step = dim.Width / 16;
2113 for (u16 x = 0; x < dim.Width; x += step) {
2114 for (u16 y = 0; y < dim.Width; y += step) {
2115 c = image->getPixel(x,y);
2116 if (c.getAlpha() > 0) {
2126 c.setRed(tR / total);
2127 c.setGreen(tG / total);
2128 c.setBlue(tB / total);
2135 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2137 std::string tname = "__shaderFlagsTexture";
2138 tname += normalmap_present ? "1" : "0";
2140 if (isKnownSourceImage(tname)) {
2141 return getTexture(tname);
2143 video::IVideoDriver *driver = m_device->getVideoDriver();
2144 video::IImage *flags_image = driver->createImage(
2145 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2146 sanity_check(flags_image != NULL);
2147 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2148 flags_image->setPixel(0, 0, c);
2149 insertSourceImage(tname, flags_image);
2150 flags_image->drop();
2151 return getTexture(tname);