3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
35 #include "util/string.h" // for parseColorString()
36 #include "imagefilters.h"
37 #include "guiscalingfilter.h"
44 A cache from texture name to texture path
46 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
49 Replaces the filename extension.
51 std::string image = "a/image.png"
52 replace_ext(image, "jpg")
53 -> image = "a/image.jpg"
54 Returns true on success.
56 static bool replace_ext(std::string &path, const char *ext)
60 // Find place of last dot, fail if \ or / found.
62 for (s32 i=path.size()-1; i>=0; i--)
70 if (path[i] == '\\' || path[i] == '/')
73 // If not found, return an empty string
76 // Else make the new path
77 path = path.substr(0, last_dot_i+1) + ext;
82 Find out the full path of an image by trying different filename
87 std::string getImagePath(std::string path)
89 // A NULL-ended list of possible image extensions
90 const char *extensions[] = {
91 "png", "jpg", "bmp", "tga",
92 "pcx", "ppm", "psd", "wal", "rgb",
95 // If there is no extension, add one
96 if (removeStringEnd(path, extensions) == "")
98 // Check paths until something is found to exist
99 const char **ext = extensions;
101 bool r = replace_ext(path, *ext);
104 if (fs::PathExists(path))
107 while((++ext) != NULL);
113 Gets the path to a texture by first checking if the texture exists
114 in texture_path and if not, using the data path.
116 Checks all supported extensions by replacing the original extension.
118 If not found, returns "".
120 Utilizes a thread-safe cache.
122 std::string getTexturePath(const std::string &filename)
124 std::string fullpath = "";
128 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
133 Check from texture_path
135 std::string texture_path = g_settings->get("texture_path");
136 if (texture_path != "")
138 std::string testpath = texture_path + DIR_DELIM + filename;
139 // Check all filename extensions. Returns "" if not found.
140 fullpath = getImagePath(testpath);
144 Check from default data directory
148 std::string base_path = porting::path_share + DIR_DELIM + "textures"
149 + DIR_DELIM + "base" + DIR_DELIM + "pack";
150 std::string testpath = base_path + DIR_DELIM + filename;
151 // Check all filename extensions. Returns "" if not found.
152 fullpath = getImagePath(testpath);
155 // Add to cache (also an empty result is cached)
156 g_texturename_to_path_cache.set(filename, fullpath);
162 void clearTextureNameCache()
164 g_texturename_to_path_cache.clear();
168 Stores internal information about a texture.
174 video::ITexture *texture;
177 const std::string &name_,
178 video::ITexture *texture_=NULL
187 SourceImageCache: A cache used for storing source images.
190 class SourceImageCache
193 ~SourceImageCache() {
194 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
195 iter != m_images.end(); iter++) {
196 iter->second->drop();
200 void insert(const std::string &name, video::IImage *img,
201 bool prefer_local, video::IVideoDriver *driver)
203 assert(img); // Pre-condition
205 std::map<std::string, video::IImage*>::iterator n;
206 n = m_images.find(name);
207 if (n != m_images.end()){
212 video::IImage* toadd = img;
213 bool need_to_grab = true;
215 // Try to use local texture instead if asked to
217 std::string path = getTexturePath(name);
219 video::IImage *img2 = driver->createImageFromFile(path.c_str());
222 need_to_grab = false;
229 m_images[name] = toadd;
231 video::IImage* get(const std::string &name)
233 std::map<std::string, video::IImage*>::iterator n;
234 n = m_images.find(name);
235 if (n != m_images.end())
239 // Primarily fetches from cache, secondarily tries to read from filesystem
240 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
242 std::map<std::string, video::IImage*>::iterator n;
243 n = m_images.find(name);
244 if (n != m_images.end()){
245 n->second->grab(); // Grab for caller
248 video::IVideoDriver* driver = device->getVideoDriver();
249 std::string path = getTexturePath(name);
251 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
252 <<name<<"\""<<std::endl;
255 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
257 video::IImage *img = driver->createImageFromFile(path.c_str());
260 m_images[name] = img;
261 img->grab(); // Grab for caller
266 std::map<std::string, video::IImage*> m_images;
273 class TextureSource : public IWritableTextureSource
276 TextureSource(IrrlichtDevice *device);
277 virtual ~TextureSource();
281 Now, assume a texture with the id 1 exists, and has the name
282 "stone.png^mineral1".
283 Then a random thread calls getTextureId for a texture called
284 "stone.png^mineral1^crack0".
285 ...Now, WTF should happen? Well:
286 - getTextureId strips off stuff recursively from the end until
287 the remaining part is found, or nothing is left when
288 something is stripped out
290 But it is slow to search for textures by names and modify them
292 - ContentFeatures is made to contain ids for the basic plain
294 - Crack textures can be slow by themselves, but the framework
298 - Assume a texture with the id 1 exists, and has the name
299 "stone.png^mineral_coal.png".
300 - Now getNodeTile() stumbles upon a node which uses
301 texture id 1, and determines that MATERIAL_FLAG_CRACK
302 must be applied to the tile
303 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
304 has received the current crack level 0 from the client. It
305 finds out the name of the texture with getTextureName(1),
306 appends "^crack0" to it and gets a new texture id with
307 getTextureId("stone.png^mineral_coal.png^crack0").
312 Gets a texture id from cache or
313 - if main thread, generates the texture, adds to cache and returns id.
314 - if other thread, adds to request queue and waits for main thread.
316 The id 0 points to a NULL texture. It is returned in case of error.
318 u32 getTextureId(const std::string &name);
320 // Finds out the name of a cached texture.
321 std::string getTextureName(u32 id);
324 If texture specified by the name pointed by the id doesn't
325 exist, create it, then return the cached texture.
327 Can be called from any thread. If called from some other thread
328 and not found in cache, the call is queued to the main thread
331 video::ITexture* getTexture(u32 id);
333 video::ITexture* getTexture(const std::string &name, u32 *id);
336 Get a texture specifically intended for mesh
337 application, i.e. not HUD, compositing, or other 2D
338 use. This texture may be a different size and may
339 have had additional filters applied.
341 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
343 // Returns a pointer to the irrlicht device
344 virtual IrrlichtDevice* getDevice()
349 bool isKnownSourceImage(const std::string &name)
351 bool is_known = false;
352 bool cache_found = m_source_image_existence.get(name, &is_known);
355 // Not found in cache; find out if a local file exists
356 is_known = (getTexturePath(name) != "");
357 m_source_image_existence.set(name, is_known);
361 // Processes queued texture requests from other threads.
362 // Shall be called from the main thread.
365 // Insert an image into the cache without touching the filesystem.
366 // Shall be called from the main thread.
367 void insertSourceImage(const std::string &name, video::IImage *img);
369 // Rebuild images and textures from the current set of source images
370 // Shall be called from the main thread.
371 void rebuildImagesAndTextures();
373 // Render a mesh to a texture.
374 // Returns NULL if render-to-texture failed.
375 // Shall be called from the main thread.
376 video::ITexture* generateTextureFromMesh(
377 const TextureFromMeshParams ¶ms);
379 // Generates an image from a full string like
380 // "stone.png^mineral_coal.png^[crack:1:0".
381 // Shall be called from the main thread.
382 video::IImage* generateImage(const std::string &name);
384 video::ITexture* getNormalTexture(const std::string &name);
387 // The id of the thread that is allowed to use irrlicht directly
388 threadid_t m_main_thread;
389 // The irrlicht device
390 IrrlichtDevice *m_device;
392 // Cache of source images
393 // This should be only accessed from the main thread
394 SourceImageCache m_sourcecache;
396 // Generate a texture
397 u32 generateTexture(const std::string &name);
399 // Generate image based on a string like "stone.png" or "[crack:1:0".
400 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
401 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
403 // Thread-safe cache of what source images are known (true = known)
404 MutexedMap<std::string, bool> m_source_image_existence;
406 // A texture id is index in this array.
407 // The first position contains a NULL texture.
408 std::vector<TextureInfo> m_textureinfo_cache;
409 // Maps a texture name to an index in the former.
410 std::map<std::string, u32> m_name_to_id;
411 // The two former containers are behind this mutex
412 JMutex m_textureinfo_cache_mutex;
414 // Queued texture fetches (to be processed by the main thread)
415 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
417 // Textures that have been overwritten with other ones
418 // but can't be deleted because the ITexture* might still be used
419 std::vector<video::ITexture*> m_texture_trash;
421 // Cached settings needed for making textures from meshes
422 bool m_setting_trilinear_filter;
423 bool m_setting_bilinear_filter;
424 bool m_setting_anisotropic_filter;
427 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
429 return new TextureSource(device);
432 TextureSource::TextureSource(IrrlichtDevice *device):
435 assert(m_device); // Pre-condition
437 m_main_thread = get_current_thread_id();
439 // Add a NULL TextureInfo as the first index, named ""
440 m_textureinfo_cache.push_back(TextureInfo(""));
441 m_name_to_id[""] = 0;
443 // Cache some settings
444 // Note: Since this is only done once, the game must be restarted
445 // for these settings to take effect
446 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
447 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
448 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
451 TextureSource::~TextureSource()
453 video::IVideoDriver* driver = m_device->getVideoDriver();
455 unsigned int textures_before = driver->getTextureCount();
457 for (std::vector<TextureInfo>::iterator iter =
458 m_textureinfo_cache.begin();
459 iter != m_textureinfo_cache.end(); iter++)
463 driver->removeTexture(iter->texture);
465 m_textureinfo_cache.clear();
467 for (std::vector<video::ITexture*>::iterator iter =
468 m_texture_trash.begin(); iter != m_texture_trash.end();
470 video::ITexture *t = *iter;
472 //cleanup trashed texture
473 driver->removeTexture(t);
476 infostream << "~TextureSource() "<< textures_before << "/"
477 << driver->getTextureCount() << std::endl;
480 u32 TextureSource::getTextureId(const std::string &name)
482 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
486 See if texture already exists
488 JMutexAutoLock lock(m_textureinfo_cache_mutex);
489 std::map<std::string, u32>::iterator n;
490 n = m_name_to_id.find(name);
491 if (n != m_name_to_id.end())
500 if (get_current_thread_id() == m_main_thread)
502 return generateTexture(name);
506 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
508 // We're gonna ask the result to be put into here
509 static ResultQueue<std::string, u32, u8, u8> result_queue;
511 // Throw a request in
512 m_get_texture_queue.add(name, 0, 0, &result_queue);
514 /*infostream<<"Waiting for texture from main thread, name=\""
515 <<name<<"\""<<std::endl;*/
520 // Wait result for a second
521 GetResult<std::string, u32, u8, u8>
522 result = result_queue.pop_front(1000);
524 if (result.key == name) {
529 catch(ItemNotFoundException &e)
531 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
536 infostream<<"getTextureId(): Failed"<<std::endl;
541 // Draw an image on top of an another one, using the alpha channel of the
543 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
544 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
546 // Like blit_with_alpha, but only modifies destination pixels that
548 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
549 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
551 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
552 // and modifies any destination pixels that are not fully transparent
553 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
554 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
556 // Apply a mask to an image
557 static void apply_mask(video::IImage *mask, video::IImage *dst,
558 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
560 // Draw or overlay a crack
561 static void draw_crack(video::IImage *crack, video::IImage *dst,
562 bool use_overlay, s32 frame_count, s32 progression,
563 video::IVideoDriver *driver);
566 void brighten(video::IImage *image);
567 // Parse a transform name
568 u32 parseImageTransform(const std::string& s);
569 // Apply transform to image dimension
570 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
571 // Apply transform to image data
572 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
575 This method generates all the textures
577 u32 TextureSource::generateTexture(const std::string &name)
579 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
581 // Empty name means texture 0
583 infostream<<"generateTexture(): name is empty"<<std::endl;
589 See if texture already exists
591 JMutexAutoLock lock(m_textureinfo_cache_mutex);
592 std::map<std::string, u32>::iterator n;
593 n = m_name_to_id.find(name);
594 if (n != m_name_to_id.end()) {
600 Calling only allowed from main thread
602 if (get_current_thread_id() != m_main_thread) {
603 errorstream<<"TextureSource::generateTexture() "
604 "called not from main thread"<<std::endl;
608 video::IVideoDriver *driver = m_device->getVideoDriver();
609 sanity_check(driver);
611 video::IImage *img = generateImage(name);
613 video::ITexture *tex = NULL;
617 img = Align2Npot2(img, driver);
619 // Create texture from resulting image
620 tex = driver->addTexture(name.c_str(), img);
621 guiScalingCache(io::path(name.c_str()), driver, img);
626 Add texture to caches (add NULL textures too)
629 JMutexAutoLock lock(m_textureinfo_cache_mutex);
631 u32 id = m_textureinfo_cache.size();
632 TextureInfo ti(name, tex);
633 m_textureinfo_cache.push_back(ti);
634 m_name_to_id[name] = id;
639 std::string TextureSource::getTextureName(u32 id)
641 JMutexAutoLock lock(m_textureinfo_cache_mutex);
643 if (id >= m_textureinfo_cache.size())
645 errorstream<<"TextureSource::getTextureName(): id="<<id
646 <<" >= m_textureinfo_cache.size()="
647 <<m_textureinfo_cache.size()<<std::endl;
651 return m_textureinfo_cache[id].name;
654 video::ITexture* TextureSource::getTexture(u32 id)
656 JMutexAutoLock lock(m_textureinfo_cache_mutex);
658 if (id >= m_textureinfo_cache.size())
661 return m_textureinfo_cache[id].texture;
664 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
666 u32 actual_id = getTextureId(name);
670 return getTexture(actual_id);
673 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
675 return getTexture(name + "^[applyfiltersformesh", id);
678 void TextureSource::processQueue()
683 //NOTE this is only thread safe for ONE consumer thread!
684 if (!m_get_texture_queue.empty())
686 GetRequest<std::string, u32, u8, u8>
687 request = m_get_texture_queue.pop();
689 /*infostream<<"TextureSource::processQueue(): "
690 <<"got texture request with "
691 <<"name=\""<<request.key<<"\""
694 m_get_texture_queue.pushResult(request, generateTexture(request.key));
698 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
700 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
702 sanity_check(get_current_thread_id() == m_main_thread);
704 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
705 m_source_image_existence.set(name, true);
708 void TextureSource::rebuildImagesAndTextures()
710 JMutexAutoLock lock(m_textureinfo_cache_mutex);
712 video::IVideoDriver* driver = m_device->getVideoDriver();
713 sanity_check(driver);
716 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
717 TextureInfo *ti = &m_textureinfo_cache[i];
718 video::IImage *img = generateImage(ti->name);
720 img = Align2Npot2(img, driver);
721 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
722 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
724 // Create texture from resulting image
725 video::ITexture *t = NULL;
727 t = driver->addTexture(ti->name.c_str(), img);
728 guiScalingCache(io::path(ti->name.c_str()), driver, img);
731 video::ITexture *t_old = ti->texture;
736 m_texture_trash.push_back(t_old);
740 video::ITexture* TextureSource::generateTextureFromMesh(
741 const TextureFromMeshParams ¶ms)
743 video::IVideoDriver *driver = m_device->getVideoDriver();
744 sanity_check(driver);
747 const GLubyte* renderstr = glGetString(GL_RENDERER);
748 std::string renderer((char*) renderstr);
750 // use no render to texture hack
752 (renderer.find("Adreno") != std::string::npos) ||
753 (renderer.find("Mali") != std::string::npos) ||
754 (renderer.find("Immersion") != std::string::npos) ||
755 (renderer.find("Tegra") != std::string::npos) ||
756 g_settings->getBool("inventory_image_hack")
758 // Get a scene manager
759 scene::ISceneManager *smgr_main = m_device->getSceneManager();
760 sanity_check(smgr_main);
761 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
764 const float scaling = 0.2;
766 scene::IMeshSceneNode* meshnode =
767 smgr->addMeshSceneNode(params.mesh, NULL,
768 -1, v3f(0,0,0), v3f(0,0,0),
769 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
770 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
771 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
772 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
773 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
774 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
776 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
777 params.camera_position, params.camera_lookat);
778 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
779 camera->setProjectionMatrix(params.camera_projection_matrix, false);
781 smgr->setAmbientLight(params.ambient_light);
782 smgr->addLightSceneNode(0,
783 params.light_position,
785 params.light_radius*scaling);
787 core::dimension2d<u32> screen = driver->getScreenSize();
790 driver->beginScene(true, true, video::SColor(0,0,0,0));
791 driver->clearZBuffer();
794 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
796 irr::video::IImage* rawImage =
797 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
799 u8* pixels = static_cast<u8*>(rawImage->lock());
806 core::rect<s32> source(
807 screen.Width /2 - (screen.Width * (scaling / 2)),
808 screen.Height/2 - (screen.Height * (scaling / 2)),
809 screen.Width /2 + (screen.Width * (scaling / 2)),
810 screen.Height/2 + (screen.Height * (scaling / 2))
813 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
814 partsize.Width, partsize.Height, GL_RGBA,
815 GL_UNSIGNED_BYTE, pixels);
819 // Drop scene manager
822 unsigned int pixelcount = partsize.Width*partsize.Height;
825 for (unsigned int i=0; i < pixelcount; i++) {
843 video::IImage* inventory_image =
844 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
846 rawImage->copyToScaling(inventory_image);
849 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
851 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
852 inventory_image->drop();
855 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
859 driver->makeColorKeyTexture(rtt, v2s32(0,0));
861 if (params.delete_texture_on_shutdown)
862 m_texture_trash.push_back(rtt);
868 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
870 static bool warned = false;
873 errorstream<<"TextureSource::generateTextureFromMesh(): "
874 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
880 // Create render target texture
881 video::ITexture *rtt = driver->addRenderTargetTexture(
882 params.dim, params.rtt_texture_name.c_str(),
883 video::ECF_A8R8G8B8);
886 errorstream<<"TextureSource::generateTextureFromMesh(): "
887 <<"addRenderTargetTexture returned NULL."<<std::endl;
892 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
893 driver->removeTexture(rtt);
894 errorstream<<"TextureSource::generateTextureFromMesh(): "
895 <<"failed to set render target"<<std::endl;
899 // Get a scene manager
900 scene::ISceneManager *smgr_main = m_device->getSceneManager();
902 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
905 scene::IMeshSceneNode* meshnode =
906 smgr->addMeshSceneNode(params.mesh, NULL,
907 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
908 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
909 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
910 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
911 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
912 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
914 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
915 params.camera_position, params.camera_lookat);
916 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
917 camera->setProjectionMatrix(params.camera_projection_matrix, false);
919 smgr->setAmbientLight(params.ambient_light);
920 smgr->addLightSceneNode(0,
921 params.light_position,
923 params.light_radius);
926 driver->beginScene(true, true, video::SColor(0,0,0,0));
930 // Drop scene manager
933 // Unset render target
934 driver->setRenderTarget(0, false, true, 0);
936 if (params.delete_texture_on_shutdown)
937 m_texture_trash.push_back(rtt);
942 video::IImage* TextureSource::generateImage(const std::string &name)
948 const char separator = '^';
949 const char paren_open = '(';
950 const char paren_close = ')';
952 // Find last separator in the name
953 s32 last_separator_pos = -1;
955 for (s32 i = name.size() - 1; i >= 0; i--) {
958 if (paren_bal == 0) {
959 last_separator_pos = i;
960 i = -1; // break out of loop
964 if (paren_bal == 0) {
965 errorstream << "generateImage(): unbalanced parentheses"
966 << "(extranous '(') while generating texture \""
967 << name << "\"" << std::endl;
980 errorstream << "generateImage(): unbalanced parentheses"
981 << "(missing matching '(') while generating texture \""
982 << name << "\"" << std::endl;
987 video::IImage *baseimg = NULL;
990 If separator was found, make the base image
991 using a recursive call.
993 if (last_separator_pos != -1) {
994 baseimg = generateImage(name.substr(0, last_separator_pos));
998 video::IVideoDriver* driver = m_device->getVideoDriver();
999 sanity_check(driver);
1002 Parse out the last part of the name of the image and act
1006 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1009 If this name is enclosed in parentheses, generate it
1010 and blit it onto the base image
1012 if (last_part_of_name[0] == paren_open
1013 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1014 std::string name2 = last_part_of_name.substr(1,
1015 last_part_of_name.size() - 2);
1016 video::IImage *tmp = generateImage(name2);
1018 errorstream << "generateImage(): "
1019 "Failed to generate \"" << name2 << "\""
1023 core::dimension2d<u32> dim = tmp->getDimension();
1025 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1026 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1028 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1029 // Generate image according to part of name
1030 errorstream << "generateImage(): "
1031 "Failed to generate \"" << last_part_of_name << "\""
1035 // If no resulting image, print a warning
1036 if (baseimg == NULL) {
1037 errorstream << "generateImage(): baseimg is NULL (attempted to"
1038 " create texture \"" << name << "\")" << std::endl;
1045 #include <GLES/gl.h>
1047 * Check and align image to npot2 if required by hardware
1048 * @param image image to check for npot2 alignment
1049 * @param driver driver to use for image operations
1050 * @return image or copy of image aligned to npot2
1052 video::IImage * Align2Npot2(video::IImage * image,
1053 video::IVideoDriver* driver)
1055 if (image == NULL) {
1059 core::dimension2d<u32> dim = image->getDimension();
1061 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1062 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1066 unsigned int height = npot2(dim.Height);
1067 unsigned int width = npot2(dim.Width);
1069 if ((dim.Height == height) &&
1070 (dim.Width == width)) {
1074 if (dim.Height > height) {
1078 if (dim.Width > width) {
1082 video::IImage *targetimage =
1083 driver->createImage(video::ECF_A8R8G8B8,
1084 core::dimension2d<u32>(width, height));
1086 if (targetimage != NULL) {
1087 image->copyToScaling(targetimage);
1095 bool TextureSource::generateImagePart(std::string part_of_name,
1096 video::IImage *& baseimg)
1098 video::IVideoDriver* driver = m_device->getVideoDriver();
1099 sanity_check(driver);
1101 // Stuff starting with [ are special commands
1102 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1104 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1106 image = Align2Npot2(image, driver);
1108 if (image == NULL) {
1109 if (part_of_name != "") {
1110 if (part_of_name.find("_normal.png") == std::string::npos){
1111 errorstream<<"generateImage(): Could not load image \""
1112 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1113 errorstream<<"generateImage(): Creating a dummy"
1114 <<" image for \""<<part_of_name<<"\""<<std::endl;
1116 infostream<<"generateImage(): Could not load normal map \""
1117 <<part_of_name<<"\""<<std::endl;
1118 infostream<<"generateImage(): Creating a dummy"
1119 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1123 // Just create a dummy image
1124 //core::dimension2d<u32> dim(2,2);
1125 core::dimension2d<u32> dim(1,1);
1126 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1127 sanity_check(image != NULL);
1128 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1129 image->setPixel(1,0, video::SColor(255,0,255,0));
1130 image->setPixel(0,1, video::SColor(255,0,0,255));
1131 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1132 image->setPixel(0,0, video::SColor(255,myrand()%256,
1133 myrand()%256,myrand()%256));
1134 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1135 myrand()%256,myrand()%256));
1136 image->setPixel(0,1, video::SColor(255,myrand()%256,
1137 myrand()%256,myrand()%256));
1138 image->setPixel(1,1, video::SColor(255,myrand()%256,
1139 myrand()%256,myrand()%256));*/
1142 // If base image is NULL, load as base.
1143 if (baseimg == NULL)
1145 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1147 Copy it this way to get an alpha channel.
1148 Otherwise images with alpha cannot be blitted on
1149 images that don't have alpha in the original file.
1151 core::dimension2d<u32> dim = image->getDimension();
1152 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1153 image->copyTo(baseimg);
1155 // Else blit on base.
1158 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1159 // Size of the copied area
1160 core::dimension2d<u32> dim = image->getDimension();
1161 //core::dimension2d<u32> dim(16,16);
1162 // Position to copy the blitted to in the base image
1163 core::position2d<s32> pos_to(0,0);
1164 // Position to copy the blitted from in the blitted image
1165 core::position2d<s32> pos_from(0,0);
1167 /*image->copyToWithAlpha(baseimg, pos_to,
1168 core::rect<s32>(pos_from, dim),
1169 video::SColor(255,255,255,255),
1171 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1178 // A special texture modification
1180 /*infostream<<"generateImage(): generating special "
1181 <<"modification \""<<part_of_name<<"\""
1187 Adds a cracking texture
1188 N = animation frame count, P = crack progression
1190 if (str_starts_with(part_of_name, "[crack"))
1192 if (baseimg == NULL) {
1193 errorstream<<"generateImagePart(): baseimg == NULL "
1194 <<"for part_of_name=\""<<part_of_name
1195 <<"\", cancelling."<<std::endl;
1199 // Crack image number and overlay option
1200 bool use_overlay = (part_of_name[6] == 'o');
1201 Strfnd sf(part_of_name);
1203 s32 frame_count = stoi(sf.next(":"));
1204 s32 progression = stoi(sf.next(":"));
1209 It is an image with a number of cracking stages
1212 video::IImage *img_crack = m_sourcecache.getOrLoad(
1213 "crack_anylength.png", m_device);
1215 if (img_crack && progression >= 0)
1217 draw_crack(img_crack, baseimg,
1218 use_overlay, frame_count,
1219 progression, driver);
1224 [combine:WxH:X,Y=filename:X,Y=filename2
1225 Creates a bigger texture from an amount of smaller ones
1227 else if (str_starts_with(part_of_name, "[combine"))
1229 Strfnd sf(part_of_name);
1231 u32 w0 = stoi(sf.next("x"));
1232 u32 h0 = stoi(sf.next(":"));
1233 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1234 core::dimension2d<u32> dim(w0,h0);
1235 if (baseimg == NULL) {
1236 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1237 baseimg->fill(video::SColor(0,0,0,0));
1239 while (sf.atend() == false) {
1240 u32 x = stoi(sf.next(","));
1241 u32 y = stoi(sf.next("="));
1242 std::string filename = sf.next(":");
1243 infostream<<"Adding \""<<filename
1244 <<"\" to combined ("<<x<<","<<y<<")"
1246 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1248 core::dimension2d<u32> dim = img->getDimension();
1249 infostream<<"Size "<<dim.Width
1250 <<"x"<<dim.Height<<std::endl;
1251 core::position2d<s32> pos_base(x, y);
1252 video::IImage *img2 =
1253 driver->createImage(video::ECF_A8R8G8B8, dim);
1256 /*img2->copyToWithAlpha(baseimg, pos_base,
1257 core::rect<s32>(v2s32(0,0), dim),
1258 video::SColor(255,255,255,255),
1260 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1263 errorstream << "generateImagePart(): Failed to load image \""
1264 << filename << "\" for [combine" << std::endl;
1271 else if (str_starts_with(part_of_name, "[brighten"))
1273 if (baseimg == NULL) {
1274 errorstream<<"generateImagePart(): baseimg==NULL "
1275 <<"for part_of_name=\""<<part_of_name
1276 <<"\", cancelling."<<std::endl;
1284 Make image completely opaque.
1285 Used for the leaves texture when in old leaves mode, so
1286 that the transparent parts don't look completely black
1287 when simple alpha channel is used for rendering.
1289 else if (str_starts_with(part_of_name, "[noalpha"))
1291 if (baseimg == NULL){
1292 errorstream<<"generateImagePart(): baseimg==NULL "
1293 <<"for part_of_name=\""<<part_of_name
1294 <<"\", cancelling."<<std::endl;
1298 core::dimension2d<u32> dim = baseimg->getDimension();
1300 // Set alpha to full
1301 for (u32 y=0; y<dim.Height; y++)
1302 for (u32 x=0; x<dim.Width; x++)
1304 video::SColor c = baseimg->getPixel(x,y);
1306 baseimg->setPixel(x,y,c);
1311 Convert one color to transparent.
1313 else if (str_starts_with(part_of_name, "[makealpha:"))
1315 if (baseimg == NULL) {
1316 errorstream<<"generateImagePart(): baseimg == NULL "
1317 <<"for part_of_name=\""<<part_of_name
1318 <<"\", cancelling."<<std::endl;
1322 Strfnd sf(part_of_name.substr(11));
1323 u32 r1 = stoi(sf.next(","));
1324 u32 g1 = stoi(sf.next(","));
1325 u32 b1 = stoi(sf.next(""));
1326 std::string filename = sf.next("");
1328 core::dimension2d<u32> dim = baseimg->getDimension();
1330 /*video::IImage *oldbaseimg = baseimg;
1331 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1332 oldbaseimg->copyTo(baseimg);
1333 oldbaseimg->drop();*/
1335 // Set alpha to full
1336 for (u32 y=0; y<dim.Height; y++)
1337 for (u32 x=0; x<dim.Width; x++)
1339 video::SColor c = baseimg->getPixel(x,y);
1341 u32 g = c.getGreen();
1342 u32 b = c.getBlue();
1343 if (!(r == r1 && g == g1 && b == b1))
1346 baseimg->setPixel(x,y,c);
1351 Rotates and/or flips the image.
1353 N can be a number (between 0 and 7) or a transform name.
1354 Rotations are counter-clockwise.
1356 1 R90 rotate by 90 degrees
1357 2 R180 rotate by 180 degrees
1358 3 R270 rotate by 270 degrees
1360 5 FXR90 flip X then rotate by 90 degrees
1362 7 FYR90 flip Y then rotate by 90 degrees
1364 Note: Transform names can be concatenated to produce
1365 their product (applies the first then the second).
1366 The resulting transform will be equivalent to one of the
1367 eight existing ones, though (see: dihedral group).
1369 else if (str_starts_with(part_of_name, "[transform"))
1371 if (baseimg == NULL) {
1372 errorstream<<"generateImagePart(): baseimg == NULL "
1373 <<"for part_of_name=\""<<part_of_name
1374 <<"\", cancelling."<<std::endl;
1378 u32 transform = parseImageTransform(part_of_name.substr(10));
1379 core::dimension2d<u32> dim = imageTransformDimension(
1380 transform, baseimg->getDimension());
1381 video::IImage *image = driver->createImage(
1382 baseimg->getColorFormat(), dim);
1383 sanity_check(image != NULL);
1384 imageTransform(transform, baseimg, image);
1389 [inventorycube{topimage{leftimage{rightimage
1390 In every subimage, replace ^ with &.
1391 Create an "inventory cube".
1392 NOTE: This should be used only on its own.
1393 Example (a grass block (not actually used in game):
1394 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1396 else if (str_starts_with(part_of_name, "[inventorycube"))
1398 if (baseimg != NULL){
1399 errorstream<<"generateImagePart(): baseimg != NULL "
1400 <<"for part_of_name=\""<<part_of_name
1401 <<"\", cancelling."<<std::endl;
1405 str_replace(part_of_name, '&', '^');
1406 Strfnd sf(part_of_name);
1408 std::string imagename_top = sf.next("{");
1409 std::string imagename_left = sf.next("{");
1410 std::string imagename_right = sf.next("{");
1412 // Generate images for the faces of the cube
1413 video::IImage *img_top = generateImage(imagename_top);
1414 video::IImage *img_left = generateImage(imagename_left);
1415 video::IImage *img_right = generateImage(imagename_right);
1417 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1418 errorstream << "generateImagePart(): Failed to create textures"
1419 << " for inventorycube \"" << part_of_name << "\""
1421 baseimg = generateImage(imagename_top);
1426 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1427 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1429 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1430 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1432 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1433 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1436 // Create textures from images
1437 video::ITexture *texture_top = driver->addTexture(
1438 (imagename_top + "__temp__").c_str(), img_top);
1439 video::ITexture *texture_left = driver->addTexture(
1440 (imagename_left + "__temp__").c_str(), img_left);
1441 video::ITexture *texture_right = driver->addTexture(
1442 (imagename_right + "__temp__").c_str(), img_right);
1443 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1451 Draw a cube mesh into a render target texture
1453 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1454 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1455 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1456 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1457 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1458 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1459 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1460 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1462 TextureFromMeshParams params;
1464 params.dim.set(64, 64);
1465 params.rtt_texture_name = part_of_name + "_RTT";
1466 // We will delete the rtt texture ourselves
1467 params.delete_texture_on_shutdown = false;
1468 params.camera_position.set(0, 1.0, -1.5);
1469 params.camera_position.rotateXZBy(45);
1470 params.camera_lookat.set(0, 0, 0);
1471 // Set orthogonal projection
1472 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1473 1.65, 1.65, 0, 100);
1475 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1476 params.light_position.set(10, 100, -50);
1477 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1478 params.light_radius = 1000;
1480 video::ITexture *rtt = generateTextureFromMesh(params);
1486 driver->removeTexture(texture_top);
1487 driver->removeTexture(texture_left);
1488 driver->removeTexture(texture_right);
1491 baseimg = generateImage(imagename_top);
1495 // Create image of render target
1496 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1497 FATAL_ERROR_IF(!image, "Could not create image of render target");
1500 driver->removeTexture(rtt);
1502 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1505 image->copyTo(baseimg);
1510 [lowpart:percent:filename
1511 Adds the lower part of a texture
1513 else if (str_starts_with(part_of_name, "[lowpart:"))
1515 Strfnd sf(part_of_name);
1517 u32 percent = stoi(sf.next(":"));
1518 std::string filename = sf.next(":");
1519 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1521 if (baseimg == NULL)
1522 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1523 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1526 core::dimension2d<u32> dim = img->getDimension();
1527 core::position2d<s32> pos_base(0, 0);
1528 video::IImage *img2 =
1529 driver->createImage(video::ECF_A8R8G8B8, dim);
1532 core::position2d<s32> clippos(0, 0);
1533 clippos.Y = dim.Height * (100-percent) / 100;
1534 core::dimension2d<u32> clipdim = dim;
1535 clipdim.Height = clipdim.Height * percent / 100 + 1;
1536 core::rect<s32> cliprect(clippos, clipdim);
1537 img2->copyToWithAlpha(baseimg, pos_base,
1538 core::rect<s32>(v2s32(0,0), dim),
1539 video::SColor(255,255,255,255),
1546 Crops a frame of a vertical animation.
1547 N = frame count, I = frame index
1549 else if (str_starts_with(part_of_name, "[verticalframe:"))
1551 Strfnd sf(part_of_name);
1553 u32 frame_count = stoi(sf.next(":"));
1554 u32 frame_index = stoi(sf.next(":"));
1556 if (baseimg == NULL){
1557 errorstream<<"generateImagePart(): baseimg != NULL "
1558 <<"for part_of_name=\""<<part_of_name
1559 <<"\", cancelling."<<std::endl;
1563 v2u32 frame_size = baseimg->getDimension();
1564 frame_size.Y /= frame_count;
1566 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1569 errorstream<<"generateImagePart(): Could not create image "
1570 <<"for part_of_name=\""<<part_of_name
1571 <<"\", cancelling."<<std::endl;
1575 // Fill target image with transparency
1576 img->fill(video::SColor(0,0,0,0));
1578 core::dimension2d<u32> dim = frame_size;
1579 core::position2d<s32> pos_dst(0, 0);
1580 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1581 baseimg->copyToWithAlpha(img, pos_dst,
1582 core::rect<s32>(pos_src, dim),
1583 video::SColor(255,255,255,255),
1591 Applies a mask to an image
1593 else if (str_starts_with(part_of_name, "[mask:"))
1595 if (baseimg == NULL) {
1596 errorstream << "generateImage(): baseimg == NULL "
1597 << "for part_of_name=\"" << part_of_name
1598 << "\", cancelling." << std::endl;
1601 Strfnd sf(part_of_name);
1603 std::string filename = sf.next(":");
1605 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1607 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1608 img->getDimension());
1610 errorstream << "generateImage(): Failed to load \""
1611 << filename << "\".";
1616 Overlays image with given color
1617 color = color as ColorString
1619 else if (str_starts_with(part_of_name, "[colorize:"))
1621 Strfnd sf(part_of_name);
1623 std::string color_str = sf.next(":");
1624 std::string ratio_str = sf.next(":");
1626 if (baseimg == NULL) {
1627 errorstream << "generateImagePart(): baseimg != NULL "
1628 << "for part_of_name=\"" << part_of_name
1629 << "\", cancelling." << std::endl;
1633 video::SColor color;
1636 if (!parseColorString(color_str, color, false))
1639 if (is_number(ratio_str))
1640 ratio = mystoi(ratio_str, 0, 255);
1642 core::dimension2d<u32> dim = baseimg->getDimension();
1643 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1646 errorstream << "generateImagePart(): Could not create image "
1647 << "for part_of_name=\"" << part_of_name
1648 << "\", cancelling." << std::endl;
1652 img->fill(video::SColor(color));
1653 // Overlay the colored image
1654 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1657 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1659 // Apply the "clean transparent" filter, if configured.
1660 if (g_settings->getBool("texture_clean_transparent"))
1661 imageCleanTransparent(baseimg, 127);
1663 /* Upscale textures to user's requested minimum size. This is a trick to make
1664 * filters look as good on low-res textures as on high-res ones, by making
1665 * low-res textures BECOME high-res ones. This is helpful for worlds that
1666 * mix high- and low-res textures, or for mods with least-common-denominator
1667 * textures that don't have the resources to offer high-res alternatives.
1669 s32 scaleto = g_settings->getS32("texture_min_size");
1671 const core::dimension2d<u32> dim = baseimg->getDimension();
1673 /* Calculate scaling needed to make the shortest texture dimension
1674 * equal to the target minimum. If e.g. this is a vertical frames
1675 * animation, the short dimension will be the real size.
1677 u32 xscale = scaleto / dim.Width;
1678 u32 yscale = scaleto / dim.Height;
1679 u32 scale = (xscale > yscale) ? xscale : yscale;
1681 // Never downscale; only scale up by 2x or more.
1683 u32 w = scale * dim.Width;
1684 u32 h = scale * dim.Height;
1685 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1686 video::IImage *newimg = driver->createImage(
1687 baseimg->getColorFormat(), newdim);
1688 baseimg->copyToScaling(newimg);
1696 errorstream << "generateImagePart(): Invalid "
1697 " modification: \"" << part_of_name << "\"" << std::endl;
1705 Draw an image on top of an another one, using the alpha channel of the
1708 This exists because IImage::copyToWithAlpha() doesn't seem to always
1711 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1712 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1714 for (u32 y0=0; y0<size.Y; y0++)
1715 for (u32 x0=0; x0<size.X; x0++)
1717 s32 src_x = src_pos.X + x0;
1718 s32 src_y = src_pos.Y + y0;
1719 s32 dst_x = dst_pos.X + x0;
1720 s32 dst_y = dst_pos.Y + y0;
1721 video::SColor src_c = src->getPixel(src_x, src_y);
1722 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1723 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1724 dst->setPixel(dst_x, dst_y, dst_c);
1729 Draw an image on top of an another one, using the alpha channel of the
1730 source image; only modify fully opaque pixels in destinaion
1732 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1733 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1735 for (u32 y0=0; y0<size.Y; y0++)
1736 for (u32 x0=0; x0<size.X; x0++)
1738 s32 src_x = src_pos.X + x0;
1739 s32 src_y = src_pos.Y + y0;
1740 s32 dst_x = dst_pos.X + x0;
1741 s32 dst_y = dst_pos.Y + y0;
1742 video::SColor src_c = src->getPixel(src_x, src_y);
1743 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1744 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1746 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1747 dst->setPixel(dst_x, dst_y, dst_c);
1753 Draw an image on top of an another one, using the specified ratio
1754 modify all partially-opaque pixels in the destination.
1756 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1757 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1759 for (u32 y0 = 0; y0 < size.Y; y0++)
1760 for (u32 x0 = 0; x0 < size.X; x0++)
1762 s32 src_x = src_pos.X + x0;
1763 s32 src_y = src_pos.Y + y0;
1764 s32 dst_x = dst_pos.X + x0;
1765 s32 dst_y = dst_pos.Y + y0;
1766 video::SColor src_c = src->getPixel(src_x, src_y);
1767 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1768 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1771 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1773 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1774 dst->setPixel(dst_x, dst_y, dst_c);
1780 Apply mask to destination
1782 static void apply_mask(video::IImage *mask, video::IImage *dst,
1783 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1785 for (u32 y0 = 0; y0 < size.Y; y0++) {
1786 for (u32 x0 = 0; x0 < size.X; x0++) {
1787 s32 mask_x = x0 + mask_pos.X;
1788 s32 mask_y = y0 + mask_pos.Y;
1789 s32 dst_x = x0 + dst_pos.X;
1790 s32 dst_y = y0 + dst_pos.Y;
1791 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1792 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1793 dst_c.color &= mask_c.color;
1794 dst->setPixel(dst_x, dst_y, dst_c);
1799 static void draw_crack(video::IImage *crack, video::IImage *dst,
1800 bool use_overlay, s32 frame_count, s32 progression,
1801 video::IVideoDriver *driver)
1803 // Dimension of destination image
1804 core::dimension2d<u32> dim_dst = dst->getDimension();
1805 // Dimension of original image
1806 core::dimension2d<u32> dim_crack = crack->getDimension();
1807 // Count of crack stages
1808 s32 crack_count = dim_crack.Height / dim_crack.Width;
1809 // Limit frame_count
1810 if (frame_count > (s32) dim_dst.Height)
1811 frame_count = dim_dst.Height;
1812 if (frame_count < 1)
1814 // Limit progression
1815 if (progression > crack_count-1)
1816 progression = crack_count-1;
1817 // Dimension of a single crack stage
1818 core::dimension2d<u32> dim_crack_cropped(
1822 // Dimension of the scaled crack stage,
1823 // which is the same as the dimension of a single destination frame
1824 core::dimension2d<u32> dim_crack_scaled(
1826 dim_dst.Height / frame_count
1828 // Create cropped and scaled crack images
1829 video::IImage *crack_cropped = driver->createImage(
1830 video::ECF_A8R8G8B8, dim_crack_cropped);
1831 video::IImage *crack_scaled = driver->createImage(
1832 video::ECF_A8R8G8B8, dim_crack_scaled);
1834 if (crack_cropped && crack_scaled)
1837 v2s32 pos_crack(0, progression*dim_crack.Width);
1838 crack->copyTo(crack_cropped,
1840 core::rect<s32>(pos_crack, dim_crack_cropped));
1841 // Scale crack image by copying
1842 crack_cropped->copyToScaling(crack_scaled);
1843 // Copy or overlay crack image onto each frame
1844 for (s32 i = 0; i < frame_count; ++i)
1846 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1849 blit_with_alpha_overlay(crack_scaled, dst,
1850 v2s32(0,0), dst_pos,
1855 blit_with_alpha(crack_scaled, dst,
1856 v2s32(0,0), dst_pos,
1863 crack_scaled->drop();
1866 crack_cropped->drop();
1869 void brighten(video::IImage *image)
1874 core::dimension2d<u32> dim = image->getDimension();
1876 for (u32 y=0; y<dim.Height; y++)
1877 for (u32 x=0; x<dim.Width; x++)
1879 video::SColor c = image->getPixel(x,y);
1880 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1881 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1882 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1883 image->setPixel(x,y,c);
1887 u32 parseImageTransform(const std::string& s)
1889 int total_transform = 0;
1891 std::string transform_names[8];
1892 transform_names[0] = "i";
1893 transform_names[1] = "r90";
1894 transform_names[2] = "r180";
1895 transform_names[3] = "r270";
1896 transform_names[4] = "fx";
1897 transform_names[6] = "fy";
1899 std::size_t pos = 0;
1900 while(pos < s.size())
1903 for (int i = 0; i <= 7; ++i)
1905 const std::string &name_i = transform_names[i];
1907 if (s[pos] == ('0' + i))
1913 else if (!(name_i.empty()) &&
1914 lowercase(s.substr(pos, name_i.size())) == name_i)
1917 pos += name_i.size();
1924 // Multiply total_transform and transform in the group D4
1927 new_total = (transform + total_transform) % 4;
1929 new_total = (transform - total_transform + 8) % 4;
1930 if ((transform >= 4) ^ (total_transform >= 4))
1933 total_transform = new_total;
1935 return total_transform;
1938 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1940 if (transform % 2 == 0)
1943 return core::dimension2d<u32>(dim.Height, dim.Width);
1946 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1948 if (src == NULL || dst == NULL)
1951 core::dimension2d<u32> dstdim = dst->getDimension();
1954 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1955 assert(transform <= 7);
1958 Compute the transformation from source coordinates (sx,sy)
1959 to destination coordinates (dx,dy).
1963 if (transform == 0) // identity
1964 sxn = 0, syn = 2; // sx = dx, sy = dy
1965 else if (transform == 1) // rotate by 90 degrees ccw
1966 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1967 else if (transform == 2) // rotate by 180 degrees
1968 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1969 else if (transform == 3) // rotate by 270 degrees ccw
1970 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1971 else if (transform == 4) // flip x
1972 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1973 else if (transform == 5) // flip x then rotate by 90 degrees ccw
1974 sxn = 2, syn = 0; // sx = dy, sy = dx
1975 else if (transform == 6) // flip y
1976 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1977 else if (transform == 7) // flip y then rotate by 90 degrees ccw
1978 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1980 for (u32 dy=0; dy<dstdim.Height; dy++)
1981 for (u32 dx=0; dx<dstdim.Width; dx++)
1983 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1984 u32 sx = entries[sxn];
1985 u32 sy = entries[syn];
1986 video::SColor c = src->getPixel(sx,sy);
1987 dst->setPixel(dx,dy,c);
1991 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1994 if (isKnownSourceImage("override_normal.png"))
1995 return getTexture("override_normal.png", &id);
1996 std::string fname_base = name;
1997 std::string normal_ext = "_normal.png";
1998 size_t pos = fname_base.find(".");
1999 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2000 if (isKnownSourceImage(fname_normal)) {
2001 // look for image extension and replace it
2003 while ((i = fname_base.find(".", i)) != std::string::npos) {
2004 fname_base.replace(i, 4, normal_ext);
2005 i += normal_ext.length();
2007 return getTexture(fname_base, &id);