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 const std::string &texture_path = g_settings->get("texture_path");
138 if (texture_path != "") {
139 std::string testpath = texture_path + DIR_DELIM + filename;
140 // Check all filename extensions. Returns "" if not found.
141 fullpath = getImagePath(testpath);
145 Check from default data directory
149 std::string base_path = porting::path_share + DIR_DELIM + "textures"
150 + DIR_DELIM + "base" + DIR_DELIM + "pack";
151 std::string testpath = base_path + DIR_DELIM + filename;
152 // Check all filename extensions. Returns "" if not found.
153 fullpath = getImagePath(testpath);
156 // Add to cache (also an empty result is cached)
157 g_texturename_to_path_cache.set(filename, fullpath);
163 void clearTextureNameCache()
165 g_texturename_to_path_cache.clear();
169 Stores internal information about a texture.
175 video::ITexture *texture;
178 const std::string &name_,
179 video::ITexture *texture_=NULL
188 SourceImageCache: A cache used for storing source images.
191 class SourceImageCache
194 ~SourceImageCache() {
195 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
196 iter != m_images.end(); ++iter) {
197 iter->second->drop();
201 void insert(const std::string &name, video::IImage *img,
202 bool prefer_local, video::IVideoDriver *driver)
204 assert(img); // Pre-condition
206 std::map<std::string, video::IImage*>::iterator n;
207 n = m_images.find(name);
208 if (n != m_images.end()){
213 video::IImage* toadd = img;
214 bool need_to_grab = true;
216 // Try to use local texture instead if asked to
218 std::string path = getTexturePath(name);
220 video::IImage *img2 = driver->createImageFromFile(path.c_str());
223 need_to_grab = false;
230 m_images[name] = toadd;
232 video::IImage* get(const std::string &name)
234 std::map<std::string, video::IImage*>::iterator n;
235 n = m_images.find(name);
236 if (n != m_images.end())
240 // Primarily fetches from cache, secondarily tries to read from filesystem
241 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
243 std::map<std::string, video::IImage*>::iterator n;
244 n = m_images.find(name);
245 if (n != m_images.end()){
246 n->second->grab(); // Grab for caller
249 video::IVideoDriver* driver = device->getVideoDriver();
250 std::string path = getTexturePath(name);
252 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
253 <<name<<"\""<<std::endl;
256 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
258 video::IImage *img = driver->createImageFromFile(path.c_str());
261 m_images[name] = img;
262 img->grab(); // Grab for caller
267 std::map<std::string, video::IImage*> m_images;
274 class TextureSource : public IWritableTextureSource
277 TextureSource(IrrlichtDevice *device);
278 virtual ~TextureSource();
282 Now, assume a texture with the id 1 exists, and has the name
283 "stone.png^mineral1".
284 Then a random thread calls getTextureId for a texture called
285 "stone.png^mineral1^crack0".
286 ...Now, WTF should happen? Well:
287 - getTextureId strips off stuff recursively from the end until
288 the remaining part is found, or nothing is left when
289 something is stripped out
291 But it is slow to search for textures by names and modify them
293 - ContentFeatures is made to contain ids for the basic plain
295 - Crack textures can be slow by themselves, but the framework
299 - Assume a texture with the id 1 exists, and has the name
300 "stone.png^mineral_coal.png".
301 - Now getNodeTile() stumbles upon a node which uses
302 texture id 1, and determines that MATERIAL_FLAG_CRACK
303 must be applied to the tile
304 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
305 has received the current crack level 0 from the client. It
306 finds out the name of the texture with getTextureName(1),
307 appends "^crack0" to it and gets a new texture id with
308 getTextureId("stone.png^mineral_coal.png^crack0").
313 Gets a texture id from cache or
314 - if main thread, generates the texture, adds to cache and returns id.
315 - if other thread, adds to request queue and waits for main thread.
317 The id 0 points to a NULL texture. It is returned in case of error.
319 u32 getTextureId(const std::string &name);
321 // Finds out the name of a cached texture.
322 std::string getTextureName(u32 id);
325 If texture specified by the name pointed by the id doesn't
326 exist, create it, then return the cached texture.
328 Can be called from any thread. If called from some other thread
329 and not found in cache, the call is queued to the main thread
332 video::ITexture* getTexture(u32 id);
334 video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
337 Get a texture specifically intended for mesh
338 application, i.e. not HUD, compositing, or other 2D
339 use. This texture may be a different size and may
340 have had additional filters applied.
342 video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
344 // Returns a pointer to the irrlicht device
345 virtual IrrlichtDevice* getDevice()
350 bool isKnownSourceImage(const std::string &name)
352 bool is_known = false;
353 bool cache_found = m_source_image_existence.get(name, &is_known);
356 // Not found in cache; find out if a local file exists
357 is_known = (getTexturePath(name) != "");
358 m_source_image_existence.set(name, is_known);
362 // Processes queued texture requests from other threads.
363 // Shall be called from the main thread.
366 // Insert an image into the cache without touching the filesystem.
367 // Shall be called from the main thread.
368 void insertSourceImage(const std::string &name, video::IImage *img);
370 // Rebuild images and textures from the current set of source images
371 // Shall be called from the main thread.
372 void rebuildImagesAndTextures();
374 // Render a mesh to a texture.
375 // Returns NULL if render-to-texture failed.
376 // Shall be called from the main thread.
377 video::ITexture* generateTextureFromMesh(
378 const TextureFromMeshParams ¶ms);
380 video::IImage* generateImage(const std::string &name);
382 video::ITexture* getNormalTexture(const std::string &name);
383 video::SColor getTextureAverageColor(const std::string &name);
384 video::ITexture *getShaderFlagsTexture(bool normamap_present);
388 // The id of the thread that is allowed to use irrlicht directly
389 threadid_t m_main_thread;
390 // The irrlicht device
391 IrrlichtDevice *m_device;
393 // Cache of source images
394 // This should be only accessed from the main thread
395 SourceImageCache m_sourcecache;
397 // Generate a texture
398 u32 generateTexture(const std::string &name);
400 // Generate image based on a string like "stone.png" or "[crack:1:0".
401 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
402 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
404 // Thread-safe cache of what source images are known (true = known)
405 MutexedMap<std::string, bool> m_source_image_existence;
407 // A texture id is index in this array.
408 // The first position contains a NULL texture.
409 std::vector<TextureInfo> m_textureinfo_cache;
410 // Maps a texture name to an index in the former.
411 std::map<std::string, u32> m_name_to_id;
412 // The two former containers are behind this mutex
413 Mutex m_textureinfo_cache_mutex;
415 // Queued texture fetches (to be processed by the main thread)
416 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
418 // Textures that have been overwritten with other ones
419 // but can't be deleted because the ITexture* might still be used
420 std::vector<video::ITexture*> m_texture_trash;
422 // Cached settings needed for making textures from meshes
423 bool m_setting_trilinear_filter;
424 bool m_setting_bilinear_filter;
425 bool m_setting_anisotropic_filter;
428 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
430 return new TextureSource(device);
433 TextureSource::TextureSource(IrrlichtDevice *device):
436 assert(m_device); // Pre-condition
438 m_main_thread = thr_get_current_thread_id();
440 // Add a NULL TextureInfo as the first index, named ""
441 m_textureinfo_cache.push_back(TextureInfo(""));
442 m_name_to_id[""] = 0;
444 // Cache some settings
445 // Note: Since this is only done once, the game must be restarted
446 // for these settings to take effect
447 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
448 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
449 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
452 TextureSource::~TextureSource()
454 video::IVideoDriver* driver = m_device->getVideoDriver();
456 unsigned int textures_before = driver->getTextureCount();
458 for (std::vector<TextureInfo>::iterator iter =
459 m_textureinfo_cache.begin();
460 iter != m_textureinfo_cache.end(); ++iter)
464 driver->removeTexture(iter->texture);
466 m_textureinfo_cache.clear();
468 for (std::vector<video::ITexture*>::iterator iter =
469 m_texture_trash.begin(); iter != m_texture_trash.end();
471 video::ITexture *t = *iter;
473 //cleanup trashed texture
474 driver->removeTexture(t);
477 infostream << "~TextureSource() "<< textures_before << "/"
478 << driver->getTextureCount() << std::endl;
481 u32 TextureSource::getTextureId(const std::string &name)
483 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
487 See if texture already exists
489 MutexAutoLock lock(m_textureinfo_cache_mutex);
490 std::map<std::string, u32>::iterator n;
491 n = m_name_to_id.find(name);
492 if (n != m_name_to_id.end())
501 if (thr_is_current_thread(m_main_thread))
503 return generateTexture(name);
507 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
509 // We're gonna ask the result to be put into here
510 static ResultQueue<std::string, u32, u8, u8> result_queue;
512 // Throw a request in
513 m_get_texture_queue.add(name, 0, 0, &result_queue);
515 /*infostream<<"Waiting for texture from main thread, name=\""
516 <<name<<"\""<<std::endl;*/
521 // Wait result for a second
522 GetResult<std::string, u32, u8, u8>
523 result = result_queue.pop_front(1000);
525 if (result.key == name) {
530 catch(ItemNotFoundException &e)
532 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
537 infostream<<"getTextureId(): Failed"<<std::endl;
542 // Draw an image on top of an another one, using the alpha channel of the
544 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
545 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
547 // Like blit_with_alpha, but only modifies destination pixels that
549 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
550 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
553 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
554 // color alpha with the destination alpha.
555 // Otherwise, any pixels that are not fully transparent get the color alpha.
556 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
557 const video::SColor &color, int ratio, bool keep_alpha);
559 // paint a texture using the given color
560 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
561 const video::SColor &color);
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, video::SColor(0,0,0,0));
943 if (params.delete_texture_on_shutdown)
944 m_texture_trash.push_back(rtt);
949 video::IImage* TextureSource::generateImage(const std::string &name)
951 // Get the base image
953 const char separator = '^';
954 const char escape = '\\';
955 const char paren_open = '(';
956 const char paren_close = ')';
958 // Find last separator in the name
959 s32 last_separator_pos = -1;
961 for (s32 i = name.size() - 1; i >= 0; i--) {
962 if (i > 0 && name[i-1] == escape)
966 if (paren_bal == 0) {
967 last_separator_pos = i;
968 i = -1; // break out of loop
972 if (paren_bal == 0) {
973 errorstream << "generateImage(): unbalanced parentheses"
974 << "(extranous '(') while generating texture \""
975 << name << "\"" << std::endl;
988 errorstream << "generateImage(): unbalanced parentheses"
989 << "(missing matching '(') while generating texture \""
990 << name << "\"" << std::endl;
995 video::IImage *baseimg = NULL;
998 If separator was found, make the base image
999 using a recursive call.
1001 if (last_separator_pos != -1) {
1002 baseimg = generateImage(name.substr(0, last_separator_pos));
1006 video::IVideoDriver* driver = m_device->getVideoDriver();
1007 sanity_check(driver);
1010 Parse out the last part of the name of the image and act
1014 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1017 If this name is enclosed in parentheses, generate it
1018 and blit it onto the base image
1020 if (last_part_of_name[0] == paren_open
1021 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1022 std::string name2 = last_part_of_name.substr(1,
1023 last_part_of_name.size() - 2);
1024 video::IImage *tmp = generateImage(name2);
1026 errorstream << "generateImage(): "
1027 "Failed to generate \"" << name2 << "\""
1031 core::dimension2d<u32> dim = tmp->getDimension();
1033 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1038 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1039 // Generate image according to part of name
1040 errorstream << "generateImage(): "
1041 "Failed to generate \"" << last_part_of_name << "\""
1045 // If no resulting image, print a warning
1046 if (baseimg == NULL) {
1047 errorstream << "generateImage(): baseimg is NULL (attempted to"
1048 " create texture \"" << name << "\")" << std::endl;
1055 #include <GLES/gl.h>
1057 * Check and align image to npot2 if required by hardware
1058 * @param image image to check for npot2 alignment
1059 * @param driver driver to use for image operations
1060 * @return image or copy of image aligned to npot2
1062 video::IImage * Align2Npot2(video::IImage * image,
1063 video::IVideoDriver* driver)
1065 if (image == NULL) {
1069 core::dimension2d<u32> dim = image->getDimension();
1071 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1072 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1076 unsigned int height = npot2(dim.Height);
1077 unsigned int width = npot2(dim.Width);
1079 if ((dim.Height == height) &&
1080 (dim.Width == width)) {
1084 if (dim.Height > height) {
1088 if (dim.Width > width) {
1092 video::IImage *targetimage =
1093 driver->createImage(video::ECF_A8R8G8B8,
1094 core::dimension2d<u32>(width, height));
1096 if (targetimage != NULL) {
1097 image->copyToScaling(targetimage);
1105 static std::string unescape_string(const std::string &str, const char esc = '\\')
1108 size_t pos = 0, cpos;
1109 out.reserve(str.size());
1111 cpos = str.find_first_of(esc, pos);
1112 if (cpos == std::string::npos) {
1113 out += str.substr(pos);
1116 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1122 bool TextureSource::generateImagePart(std::string part_of_name,
1123 video::IImage *& baseimg)
1125 const char escape = '\\'; // same as in generateImage()
1126 video::IVideoDriver* driver = m_device->getVideoDriver();
1127 sanity_check(driver);
1129 // Stuff starting with [ are special commands
1130 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1132 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1134 image = Align2Npot2(image, driver);
1136 if (image == NULL) {
1137 if (part_of_name != "") {
1138 if (part_of_name.find("_normal.png") == std::string::npos){
1139 errorstream<<"generateImage(): Could not load image \""
1140 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1141 errorstream<<"generateImage(): Creating a dummy"
1142 <<" image for \""<<part_of_name<<"\""<<std::endl;
1144 infostream<<"generateImage(): Could not load normal map \""
1145 <<part_of_name<<"\""<<std::endl;
1146 infostream<<"generateImage(): Creating a dummy"
1147 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1151 // Just create a dummy image
1152 //core::dimension2d<u32> dim(2,2);
1153 core::dimension2d<u32> dim(1,1);
1154 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1155 sanity_check(image != NULL);
1156 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1157 image->setPixel(1,0, video::SColor(255,0,255,0));
1158 image->setPixel(0,1, video::SColor(255,0,0,255));
1159 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1160 image->setPixel(0,0, video::SColor(255,myrand()%256,
1161 myrand()%256,myrand()%256));
1162 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1163 myrand()%256,myrand()%256));
1164 image->setPixel(0,1, video::SColor(255,myrand()%256,
1165 myrand()%256,myrand()%256));
1166 image->setPixel(1,1, video::SColor(255,myrand()%256,
1167 myrand()%256,myrand()%256));*/
1170 // If base image is NULL, load as base.
1171 if (baseimg == NULL)
1173 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1175 Copy it this way to get an alpha channel.
1176 Otherwise images with alpha cannot be blitted on
1177 images that don't have alpha in the original file.
1179 core::dimension2d<u32> dim = image->getDimension();
1180 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1181 image->copyTo(baseimg);
1183 // Else blit on base.
1186 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1187 // Size of the copied area
1188 core::dimension2d<u32> dim = image->getDimension();
1189 //core::dimension2d<u32> dim(16,16);
1190 // Position to copy the blitted to in the base image
1191 core::position2d<s32> pos_to(0,0);
1192 // Position to copy the blitted from in the blitted image
1193 core::position2d<s32> pos_from(0,0);
1195 /*image->copyToWithAlpha(baseimg, pos_to,
1196 core::rect<s32>(pos_from, dim),
1197 video::SColor(255,255,255,255),
1200 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1201 if (dim == dim_dst) {
1202 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1203 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1204 // Upscale overlying image
1205 video::IImage* scaled_image = m_device->getVideoDriver()->
1206 createImage(video::ECF_A8R8G8B8, dim_dst);
1207 image->copyToScaling(scaled_image);
1209 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1210 scaled_image->drop();
1212 // Upscale base image
1213 video::IImage* scaled_base = m_device->getVideoDriver()->
1214 createImage(video::ECF_A8R8G8B8, dim);
1215 baseimg->copyToScaling(scaled_base);
1217 baseimg = scaled_base;
1219 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1227 // A special texture modification
1229 /*infostream<<"generateImage(): generating special "
1230 <<"modification \""<<part_of_name<<"\""
1236 Adds a cracking texture
1237 N = animation frame count, P = crack progression
1239 if (str_starts_with(part_of_name, "[crack"))
1241 if (baseimg == NULL) {
1242 errorstream<<"generateImagePart(): baseimg == NULL "
1243 <<"for part_of_name=\""<<part_of_name
1244 <<"\", cancelling."<<std::endl;
1248 // Crack image number and overlay option
1249 bool use_overlay = (part_of_name[6] == 'o');
1250 Strfnd sf(part_of_name);
1252 s32 frame_count = stoi(sf.next(":"));
1253 s32 progression = stoi(sf.next(":"));
1255 if (progression >= 0) {
1259 It is an image with a number of cracking stages
1262 video::IImage *img_crack = m_sourcecache.getOrLoad(
1263 "crack_anylength.png", m_device);
1266 draw_crack(img_crack, baseimg,
1267 use_overlay, frame_count,
1268 progression, driver);
1274 [combine:WxH:X,Y=filename:X,Y=filename2
1275 Creates a bigger texture from any amount of smaller ones
1277 else if (str_starts_with(part_of_name, "[combine"))
1279 Strfnd sf(part_of_name);
1281 u32 w0 = stoi(sf.next("x"));
1282 u32 h0 = stoi(sf.next(":"));
1283 core::dimension2d<u32> dim(w0,h0);
1284 if (baseimg == NULL) {
1285 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1286 baseimg->fill(video::SColor(0,0,0,0));
1288 while (sf.at_end() == false) {
1289 u32 x = stoi(sf.next(","));
1290 u32 y = stoi(sf.next("="));
1291 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1292 infostream<<"Adding \""<<filename
1293 <<"\" to combined ("<<x<<","<<y<<")"
1295 video::IImage *img = generateImage(filename);
1297 core::dimension2d<u32> dim = img->getDimension();
1298 infostream<<"Size "<<dim.Width
1299 <<"x"<<dim.Height<<std::endl;
1300 core::position2d<s32> pos_base(x, y);
1301 video::IImage *img2 =
1302 driver->createImage(video::ECF_A8R8G8B8, dim);
1305 /*img2->copyToWithAlpha(baseimg, pos_base,
1306 core::rect<s32>(v2s32(0,0), dim),
1307 video::SColor(255,255,255,255),
1309 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1312 errorstream << "generateImagePart(): Failed to load image \""
1313 << filename << "\" for [combine" << std::endl;
1320 else if (str_starts_with(part_of_name, "[brighten"))
1322 if (baseimg == NULL) {
1323 errorstream<<"generateImagePart(): baseimg==NULL "
1324 <<"for part_of_name=\""<<part_of_name
1325 <<"\", cancelling."<<std::endl;
1333 Make image completely opaque.
1334 Used for the leaves texture when in old leaves mode, so
1335 that the transparent parts don't look completely black
1336 when simple alpha channel is used for rendering.
1338 else if (str_starts_with(part_of_name, "[noalpha"))
1340 if (baseimg == NULL){
1341 errorstream<<"generateImagePart(): baseimg==NULL "
1342 <<"for part_of_name=\""<<part_of_name
1343 <<"\", cancelling."<<std::endl;
1347 core::dimension2d<u32> dim = baseimg->getDimension();
1349 // Set alpha to full
1350 for (u32 y=0; y<dim.Height; y++)
1351 for (u32 x=0; x<dim.Width; x++)
1353 video::SColor c = baseimg->getPixel(x,y);
1355 baseimg->setPixel(x,y,c);
1360 Convert one color to transparent.
1362 else if (str_starts_with(part_of_name, "[makealpha:"))
1364 if (baseimg == NULL) {
1365 errorstream<<"generateImagePart(): baseimg == NULL "
1366 <<"for part_of_name=\""<<part_of_name
1367 <<"\", cancelling."<<std::endl;
1371 Strfnd sf(part_of_name.substr(11));
1372 u32 r1 = stoi(sf.next(","));
1373 u32 g1 = stoi(sf.next(","));
1374 u32 b1 = stoi(sf.next(""));
1376 core::dimension2d<u32> dim = baseimg->getDimension();
1378 /*video::IImage *oldbaseimg = baseimg;
1379 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1380 oldbaseimg->copyTo(baseimg);
1381 oldbaseimg->drop();*/
1383 // Set alpha to full
1384 for (u32 y=0; y<dim.Height; y++)
1385 for (u32 x=0; x<dim.Width; x++)
1387 video::SColor c = baseimg->getPixel(x,y);
1389 u32 g = c.getGreen();
1390 u32 b = c.getBlue();
1391 if (!(r == r1 && g == g1 && b == b1))
1394 baseimg->setPixel(x,y,c);
1399 Rotates and/or flips the image.
1401 N can be a number (between 0 and 7) or a transform name.
1402 Rotations are counter-clockwise.
1404 1 R90 rotate by 90 degrees
1405 2 R180 rotate by 180 degrees
1406 3 R270 rotate by 270 degrees
1408 5 FXR90 flip X then rotate by 90 degrees
1410 7 FYR90 flip Y then rotate by 90 degrees
1412 Note: Transform names can be concatenated to produce
1413 their product (applies the first then the second).
1414 The resulting transform will be equivalent to one of the
1415 eight existing ones, though (see: dihedral group).
1417 else if (str_starts_with(part_of_name, "[transform"))
1419 if (baseimg == NULL) {
1420 errorstream<<"generateImagePart(): baseimg == NULL "
1421 <<"for part_of_name=\""<<part_of_name
1422 <<"\", cancelling."<<std::endl;
1426 u32 transform = parseImageTransform(part_of_name.substr(10));
1427 core::dimension2d<u32> dim = imageTransformDimension(
1428 transform, baseimg->getDimension());
1429 video::IImage *image = driver->createImage(
1430 baseimg->getColorFormat(), dim);
1431 sanity_check(image != NULL);
1432 imageTransform(transform, baseimg, image);
1437 [inventorycube{topimage{leftimage{rightimage
1438 In every subimage, replace ^ with &.
1439 Create an "inventory cube".
1440 NOTE: This should be used only on its own.
1441 Example (a grass block (not actually used in game):
1442 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1444 else if (str_starts_with(part_of_name, "[inventorycube"))
1446 if (baseimg != NULL){
1447 errorstream<<"generateImagePart(): baseimg != NULL "
1448 <<"for part_of_name=\""<<part_of_name
1449 <<"\", cancelling."<<std::endl;
1453 str_replace(part_of_name, '&', '^');
1454 Strfnd sf(part_of_name);
1456 std::string imagename_top = sf.next("{");
1457 std::string imagename_left = sf.next("{");
1458 std::string imagename_right = sf.next("{");
1460 // Generate images for the faces of the cube
1461 video::IImage *img_top = generateImage(imagename_top);
1462 video::IImage *img_left = generateImage(imagename_left);
1463 video::IImage *img_right = generateImage(imagename_right);
1465 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1466 errorstream << "generateImagePart(): Failed to create textures"
1467 << " for inventorycube \"" << part_of_name << "\""
1469 baseimg = generateImage(imagename_top);
1474 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1475 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1477 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1478 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1480 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1481 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1484 // Create textures from images
1485 video::ITexture *texture_top = driver->addTexture(
1486 (imagename_top + "__temp__").c_str(), img_top);
1487 video::ITexture *texture_left = driver->addTexture(
1488 (imagename_left + "__temp__").c_str(), img_left);
1489 video::ITexture *texture_right = driver->addTexture(
1490 (imagename_right + "__temp__").c_str(), img_right);
1491 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1499 Draw a cube mesh into a render target texture
1501 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1502 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1503 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1504 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1505 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1506 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1507 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1508 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1510 TextureFromMeshParams params;
1512 params.dim.set(64, 64);
1513 params.rtt_texture_name = part_of_name + "_RTT";
1514 // We will delete the rtt texture ourselves
1515 params.delete_texture_on_shutdown = false;
1516 params.camera_position.set(0, 1.0, -1.5);
1517 params.camera_position.rotateXZBy(45);
1518 params.camera_lookat.set(0, 0, 0);
1519 // Set orthogonal projection
1520 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1521 1.65, 1.65, 0, 100);
1523 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1524 params.light_position.set(10, 100, -50);
1525 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1526 params.light_radius = 1000;
1528 video::ITexture *rtt = generateTextureFromMesh(params);
1534 driver->removeTexture(texture_top);
1535 driver->removeTexture(texture_left);
1536 driver->removeTexture(texture_right);
1539 baseimg = generateImage(imagename_top);
1543 // Create image of render target
1544 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1545 FATAL_ERROR_IF(!image, "Could not create image of render target");
1548 driver->removeTexture(rtt);
1550 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1553 image->copyTo(baseimg);
1558 [lowpart:percent:filename
1559 Adds the lower part of a texture
1561 else if (str_starts_with(part_of_name, "[lowpart:"))
1563 Strfnd sf(part_of_name);
1565 u32 percent = stoi(sf.next(":"));
1566 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1568 if (baseimg == NULL)
1569 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1570 video::IImage *img = generateImage(filename);
1573 core::dimension2d<u32> dim = img->getDimension();
1574 core::position2d<s32> pos_base(0, 0);
1575 video::IImage *img2 =
1576 driver->createImage(video::ECF_A8R8G8B8, dim);
1579 core::position2d<s32> clippos(0, 0);
1580 clippos.Y = dim.Height * (100-percent) / 100;
1581 core::dimension2d<u32> clipdim = dim;
1582 clipdim.Height = clipdim.Height * percent / 100 + 1;
1583 core::rect<s32> cliprect(clippos, clipdim);
1584 img2->copyToWithAlpha(baseimg, pos_base,
1585 core::rect<s32>(v2s32(0,0), dim),
1586 video::SColor(255,255,255,255),
1593 Crops a frame of a vertical animation.
1594 N = frame count, I = frame index
1596 else if (str_starts_with(part_of_name, "[verticalframe:"))
1598 Strfnd sf(part_of_name);
1600 u32 frame_count = stoi(sf.next(":"));
1601 u32 frame_index = stoi(sf.next(":"));
1603 if (baseimg == NULL){
1604 errorstream<<"generateImagePart(): baseimg != NULL "
1605 <<"for part_of_name=\""<<part_of_name
1606 <<"\", cancelling."<<std::endl;
1610 v2u32 frame_size = baseimg->getDimension();
1611 frame_size.Y /= frame_count;
1613 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1616 errorstream<<"generateImagePart(): Could not create image "
1617 <<"for part_of_name=\""<<part_of_name
1618 <<"\", cancelling."<<std::endl;
1622 // Fill target image with transparency
1623 img->fill(video::SColor(0,0,0,0));
1625 core::dimension2d<u32> dim = frame_size;
1626 core::position2d<s32> pos_dst(0, 0);
1627 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1628 baseimg->copyToWithAlpha(img, pos_dst,
1629 core::rect<s32>(pos_src, dim),
1630 video::SColor(255,255,255,255),
1638 Applies a mask to an image
1640 else if (str_starts_with(part_of_name, "[mask:"))
1642 if (baseimg == NULL) {
1643 errorstream << "generateImage(): baseimg == NULL "
1644 << "for part_of_name=\"" << part_of_name
1645 << "\", cancelling." << std::endl;
1648 Strfnd sf(part_of_name);
1650 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1652 video::IImage *img = generateImage(filename);
1654 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1655 img->getDimension());
1658 errorstream << "generateImage(): Failed to load \""
1659 << filename << "\".";
1664 multiplys a given color to any pixel of an image
1665 color = color as ColorString
1667 else if (str_starts_with(part_of_name, "[multiply:")) {
1668 Strfnd sf(part_of_name);
1670 std::string color_str = sf.next(":");
1672 if (baseimg == NULL) {
1673 errorstream << "generateImagePart(): baseimg != NULL "
1674 << "for part_of_name=\"" << part_of_name
1675 << "\", cancelling." << std::endl;
1679 video::SColor color;
1681 if (!parseColorString(color_str, color, false))
1684 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1688 Overlays image with given color
1689 color = color as ColorString
1691 else if (str_starts_with(part_of_name, "[colorize:"))
1693 Strfnd sf(part_of_name);
1695 std::string color_str = sf.next(":");
1696 std::string ratio_str = sf.next(":");
1698 if (baseimg == NULL) {
1699 errorstream << "generateImagePart(): baseimg != NULL "
1700 << "for part_of_name=\"" << part_of_name
1701 << "\", cancelling." << std::endl;
1705 video::SColor color;
1707 bool keep_alpha = false;
1709 if (!parseColorString(color_str, color, false))
1712 if (is_number(ratio_str))
1713 ratio = mystoi(ratio_str, 0, 255);
1714 else if (ratio_str == "alpha")
1717 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1720 [applyfiltersformesh
1723 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1725 // Apply the "clean transparent" filter, if configured.
1726 if (g_settings->getBool("texture_clean_transparent"))
1727 imageCleanTransparent(baseimg, 127);
1729 /* Upscale textures to user's requested minimum size. This is a trick to make
1730 * filters look as good on low-res textures as on high-res ones, by making
1731 * low-res textures BECOME high-res ones. This is helpful for worlds that
1732 * mix high- and low-res textures, or for mods with least-common-denominator
1733 * textures that don't have the resources to offer high-res alternatives.
1735 s32 scaleto = g_settings->getS32("texture_min_size");
1737 const core::dimension2d<u32> dim = baseimg->getDimension();
1739 /* Calculate scaling needed to make the shortest texture dimension
1740 * equal to the target minimum. If e.g. this is a vertical frames
1741 * animation, the short dimension will be the real size.
1743 if ((dim.Width == 0) || (dim.Height == 0)) {
1744 errorstream << "generateImagePart(): Illegal 0 dimension "
1745 << "for part_of_name=\""<< part_of_name
1746 << "\", cancelling." << std::endl;
1749 u32 xscale = scaleto / dim.Width;
1750 u32 yscale = scaleto / dim.Height;
1751 u32 scale = (xscale > yscale) ? xscale : yscale;
1753 // Never downscale; only scale up by 2x or more.
1755 u32 w = scale * dim.Width;
1756 u32 h = scale * dim.Height;
1757 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1758 video::IImage *newimg = driver->createImage(
1759 baseimg->getColorFormat(), newdim);
1760 baseimg->copyToScaling(newimg);
1768 Resizes the base image to the given dimensions
1770 else if (str_starts_with(part_of_name, "[resize"))
1772 if (baseimg == NULL) {
1773 errorstream << "generateImagePart(): baseimg == NULL "
1774 << "for part_of_name=\""<< part_of_name
1775 << "\", cancelling." << std::endl;
1779 Strfnd sf(part_of_name);
1781 u32 width = stoi(sf.next("x"));
1782 u32 height = stoi(sf.next(""));
1783 core::dimension2d<u32> dim(width, height);
1785 video::IImage* image = m_device->getVideoDriver()->
1786 createImage(video::ECF_A8R8G8B8, dim);
1787 baseimg->copyToScaling(image);
1793 Makes the base image transparent according to the given ratio.
1794 R must be between 0 and 255.
1795 0 means totally transparent.
1796 255 means totally opaque.
1798 else if (str_starts_with(part_of_name, "[opacity:")) {
1799 if (baseimg == NULL) {
1800 errorstream << "generateImagePart(): baseimg == NULL "
1801 << "for part_of_name=\"" << part_of_name
1802 << "\", cancelling." << std::endl;
1806 Strfnd sf(part_of_name);
1809 u32 ratio = mystoi(sf.next(""), 0, 255);
1811 core::dimension2d<u32> dim = baseimg->getDimension();
1813 for (u32 y = 0; y < dim.Height; y++)
1814 for (u32 x = 0; x < dim.Width; x++)
1816 video::SColor c = baseimg->getPixel(x, y);
1817 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1818 baseimg->setPixel(x, y, c);
1823 Inverts the given channels of the base image.
1824 Mode may contain the characters "r", "g", "b", "a".
1825 Only the channels that are mentioned in the mode string
1828 else if (str_starts_with(part_of_name, "[invert:")) {
1829 if (baseimg == NULL) {
1830 errorstream << "generateImagePart(): baseimg == NULL "
1831 << "for part_of_name=\"" << part_of_name
1832 << "\", cancelling." << std::endl;
1836 Strfnd sf(part_of_name);
1839 std::string mode = sf.next("");
1841 if (mode.find("a") != std::string::npos)
1842 mask |= 0xff000000UL;
1843 if (mode.find("r") != std::string::npos)
1844 mask |= 0x00ff0000UL;
1845 if (mode.find("g") != std::string::npos)
1846 mask |= 0x0000ff00UL;
1847 if (mode.find("b") != std::string::npos)
1848 mask |= 0x000000ffUL;
1850 core::dimension2d<u32> dim = baseimg->getDimension();
1852 for (u32 y = 0; y < dim.Height; y++)
1853 for (u32 x = 0; x < dim.Width; x++)
1855 video::SColor c = baseimg->getPixel(x, y);
1857 baseimg->setPixel(x, y, c);
1862 Retrieves a tile at position X,Y (in tiles)
1863 from the base image it assumes to be a
1864 tilesheet with dimensions W,H (in tiles).
1866 else if (part_of_name.substr(0,7) == "[sheet:") {
1867 if (baseimg == NULL) {
1868 errorstream << "generateImagePart(): baseimg != NULL "
1869 << "for part_of_name=\"" << part_of_name
1870 << "\", cancelling." << std::endl;
1874 Strfnd sf(part_of_name);
1876 u32 w0 = stoi(sf.next("x"));
1877 u32 h0 = stoi(sf.next(":"));
1878 u32 x0 = stoi(sf.next(","));
1879 u32 y0 = stoi(sf.next(":"));
1881 core::dimension2d<u32> img_dim = baseimg->getDimension();
1882 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1884 video::IImage *img = driver->createImage(
1885 video::ECF_A8R8G8B8, tile_dim);
1887 errorstream << "generateImagePart(): Could not create image "
1888 << "for part_of_name=\"" << part_of_name
1889 << "\", cancelling." << std::endl;
1893 img->fill(video::SColor(0,0,0,0));
1894 v2u32 vdim(tile_dim);
1895 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1896 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1897 video::SColor(255,255,255,255), NULL);
1905 errorstream << "generateImagePart(): Invalid "
1906 " modification: \"" << part_of_name << "\"" << std::endl;
1914 Draw an image on top of an another one, using the alpha channel of the
1917 This exists because IImage::copyToWithAlpha() doesn't seem to always
1920 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1921 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1923 for (u32 y0=0; y0<size.Y; y0++)
1924 for (u32 x0=0; x0<size.X; x0++)
1926 s32 src_x = src_pos.X + x0;
1927 s32 src_y = src_pos.Y + y0;
1928 s32 dst_x = dst_pos.X + x0;
1929 s32 dst_y = dst_pos.Y + y0;
1930 video::SColor src_c = src->getPixel(src_x, src_y);
1931 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1932 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1933 dst->setPixel(dst_x, dst_y, dst_c);
1938 Draw an image on top of an another one, using the alpha channel of the
1939 source image; only modify fully opaque pixels in destinaion
1941 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1942 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1944 for (u32 y0=0; y0<size.Y; y0++)
1945 for (u32 x0=0; x0<size.X; x0++)
1947 s32 src_x = src_pos.X + x0;
1948 s32 src_y = src_pos.Y + y0;
1949 s32 dst_x = dst_pos.X + x0;
1950 s32 dst_y = dst_pos.Y + y0;
1951 video::SColor src_c = src->getPixel(src_x, src_y);
1952 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1953 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1955 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1956 dst->setPixel(dst_x, dst_y, dst_c);
1961 // This function has been disabled because it is currently unused.
1962 // Feel free to re-enable if you find it handy.
1965 Draw an image on top of an another one, using the specified ratio
1966 modify all partially-opaque pixels in the destination.
1968 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1969 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1971 for (u32 y0 = 0; y0 < size.Y; y0++)
1972 for (u32 x0 = 0; x0 < size.X; x0++)
1974 s32 src_x = src_pos.X + x0;
1975 s32 src_y = src_pos.Y + y0;
1976 s32 dst_x = dst_pos.X + x0;
1977 s32 dst_y = dst_pos.Y + y0;
1978 video::SColor src_c = src->getPixel(src_x, src_y);
1979 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1980 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1983 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1985 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1986 dst->setPixel(dst_x, dst_y, dst_c);
1993 Apply color to destination
1995 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1996 const video::SColor &color, int ratio, bool keep_alpha)
1998 u32 alpha = color.getAlpha();
1999 video::SColor dst_c;
2000 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2001 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2003 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2004 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2005 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2006 if (dst_alpha > 0) {
2007 dst_c.setAlpha(dst_alpha * alpha / 255);
2008 dst->setPixel(x, y, dst_c);
2011 } else { // replace the color including the alpha
2012 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2013 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2014 if (dst->getPixel(x, y).getAlpha() > 0)
2015 dst->setPixel(x, y, color);
2017 } else { // interpolate between the color and destination
2018 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2019 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2020 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2021 dst_c = dst->getPixel(x, y);
2022 if (dst_c.getAlpha() > 0) {
2023 dst_c = color.getInterpolated(dst_c, interp);
2024 dst->setPixel(x, y, dst_c);
2031 Apply color to destination
2033 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2034 const video::SColor &color)
2036 video::SColor dst_c;
2038 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2039 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2040 dst_c = dst->getPixel(x, y);
2043 (dst_c.getRed() * color.getRed()) / 255,
2044 (dst_c.getGreen() * color.getGreen()) / 255,
2045 (dst_c.getBlue() * color.getBlue()) / 255
2047 dst->setPixel(x, y, dst_c);
2052 Apply mask to destination
2054 static void apply_mask(video::IImage *mask, video::IImage *dst,
2055 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2057 for (u32 y0 = 0; y0 < size.Y; y0++) {
2058 for (u32 x0 = 0; x0 < size.X; x0++) {
2059 s32 mask_x = x0 + mask_pos.X;
2060 s32 mask_y = y0 + mask_pos.Y;
2061 s32 dst_x = x0 + dst_pos.X;
2062 s32 dst_y = y0 + dst_pos.Y;
2063 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2064 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2065 dst_c.color &= mask_c.color;
2066 dst->setPixel(dst_x, dst_y, dst_c);
2071 static void draw_crack(video::IImage *crack, video::IImage *dst,
2072 bool use_overlay, s32 frame_count, s32 progression,
2073 video::IVideoDriver *driver)
2075 // Dimension of destination image
2076 core::dimension2d<u32> dim_dst = dst->getDimension();
2077 // Dimension of original image
2078 core::dimension2d<u32> dim_crack = crack->getDimension();
2079 // Count of crack stages
2080 s32 crack_count = dim_crack.Height / dim_crack.Width;
2081 // Limit frame_count
2082 if (frame_count > (s32) dim_dst.Height)
2083 frame_count = dim_dst.Height;
2084 if (frame_count < 1)
2086 // Limit progression
2087 if (progression > crack_count-1)
2088 progression = crack_count-1;
2089 // Dimension of a single crack stage
2090 core::dimension2d<u32> dim_crack_cropped(
2094 // Dimension of the scaled crack stage,
2095 // which is the same as the dimension of a single destination frame
2096 core::dimension2d<u32> dim_crack_scaled(
2098 dim_dst.Height / frame_count
2100 // Create cropped and scaled crack images
2101 video::IImage *crack_cropped = driver->createImage(
2102 video::ECF_A8R8G8B8, dim_crack_cropped);
2103 video::IImage *crack_scaled = driver->createImage(
2104 video::ECF_A8R8G8B8, dim_crack_scaled);
2106 if (crack_cropped && crack_scaled)
2109 v2s32 pos_crack(0, progression*dim_crack.Width);
2110 crack->copyTo(crack_cropped,
2112 core::rect<s32>(pos_crack, dim_crack_cropped));
2113 // Scale crack image by copying
2114 crack_cropped->copyToScaling(crack_scaled);
2115 // Copy or overlay crack image onto each frame
2116 for (s32 i = 0; i < frame_count; ++i)
2118 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2121 blit_with_alpha_overlay(crack_scaled, dst,
2122 v2s32(0,0), dst_pos,
2127 blit_with_alpha(crack_scaled, dst,
2128 v2s32(0,0), dst_pos,
2135 crack_scaled->drop();
2138 crack_cropped->drop();
2141 void brighten(video::IImage *image)
2146 core::dimension2d<u32> dim = image->getDimension();
2148 for (u32 y=0; y<dim.Height; y++)
2149 for (u32 x=0; x<dim.Width; x++)
2151 video::SColor c = image->getPixel(x,y);
2152 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2153 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2154 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2155 image->setPixel(x,y,c);
2159 u32 parseImageTransform(const std::string& s)
2161 int total_transform = 0;
2163 std::string transform_names[8];
2164 transform_names[0] = "i";
2165 transform_names[1] = "r90";
2166 transform_names[2] = "r180";
2167 transform_names[3] = "r270";
2168 transform_names[4] = "fx";
2169 transform_names[6] = "fy";
2171 std::size_t pos = 0;
2172 while(pos < s.size())
2175 for (int i = 0; i <= 7; ++i)
2177 const std::string &name_i = transform_names[i];
2179 if (s[pos] == ('0' + i))
2185 else if (!(name_i.empty()) &&
2186 lowercase(s.substr(pos, name_i.size())) == name_i)
2189 pos += name_i.size();
2196 // Multiply total_transform and transform in the group D4
2199 new_total = (transform + total_transform) % 4;
2201 new_total = (transform - total_transform + 8) % 4;
2202 if ((transform >= 4) ^ (total_transform >= 4))
2205 total_transform = new_total;
2207 return total_transform;
2210 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2212 if (transform % 2 == 0)
2215 return core::dimension2d<u32>(dim.Height, dim.Width);
2218 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2220 if (src == NULL || dst == NULL)
2223 core::dimension2d<u32> dstdim = dst->getDimension();
2226 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2227 assert(transform <= 7);
2230 Compute the transformation from source coordinates (sx,sy)
2231 to destination coordinates (dx,dy).
2235 if (transform == 0) // identity
2236 sxn = 0, syn = 2; // sx = dx, sy = dy
2237 else if (transform == 1) // rotate by 90 degrees ccw
2238 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2239 else if (transform == 2) // rotate by 180 degrees
2240 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2241 else if (transform == 3) // rotate by 270 degrees ccw
2242 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2243 else if (transform == 4) // flip x
2244 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2245 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2246 sxn = 2, syn = 0; // sx = dy, sy = dx
2247 else if (transform == 6) // flip y
2248 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2249 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2250 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2252 for (u32 dy=0; dy<dstdim.Height; dy++)
2253 for (u32 dx=0; dx<dstdim.Width; dx++)
2255 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2256 u32 sx = entries[sxn];
2257 u32 sy = entries[syn];
2258 video::SColor c = src->getPixel(sx,sy);
2259 dst->setPixel(dx,dy,c);
2263 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2265 if (isKnownSourceImage("override_normal.png"))
2266 return getTexture("override_normal.png");
2267 std::string fname_base = name;
2268 static const char *normal_ext = "_normal.png";
2269 static const u32 normal_ext_size = strlen(normal_ext);
2270 size_t pos = fname_base.find(".");
2271 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2272 if (isKnownSourceImage(fname_normal)) {
2273 // look for image extension and replace it
2275 while ((i = fname_base.find(".", i)) != std::string::npos) {
2276 fname_base.replace(i, 4, normal_ext);
2277 i += normal_ext_size;
2279 return getTexture(fname_base);
2284 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2286 video::IVideoDriver *driver = m_device->getVideoDriver();
2287 video::SColor c(0, 0, 0, 0);
2288 video::ITexture *texture = getTexture(name);
2289 video::IImage *image = driver->createImage(texture,
2290 core::position2d<s32>(0, 0),
2291 texture->getOriginalSize());
2296 core::dimension2d<u32> dim = image->getDimension();
2299 step = dim.Width / 16;
2300 for (u16 x = 0; x < dim.Width; x += step) {
2301 for (u16 y = 0; y < dim.Width; y += step) {
2302 c = image->getPixel(x,y);
2303 if (c.getAlpha() > 0) {
2313 c.setRed(tR / total);
2314 c.setGreen(tG / total);
2315 c.setBlue(tB / total);
2322 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2324 std::string tname = "__shaderFlagsTexture";
2325 tname += normalmap_present ? "1" : "0";
2327 if (isKnownSourceImage(tname)) {
2328 return getTexture(tname);
2330 video::IVideoDriver *driver = m_device->getVideoDriver();
2331 video::IImage *flags_image = driver->createImage(
2332 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2333 sanity_check(flags_image != NULL);
2334 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2335 flags_image->setPixel(0, 0, c);
2336 insertSourceImage(tname, flags_image);
2337 flags_image->drop();
2338 return getTexture(tname);