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 virtual Palette* getPalette(const std::string &name);
346 // Returns a pointer to the irrlicht device
347 virtual IrrlichtDevice* getDevice()
352 bool isKnownSourceImage(const std::string &name)
354 bool is_known = false;
355 bool cache_found = m_source_image_existence.get(name, &is_known);
358 // Not found in cache; find out if a local file exists
359 is_known = (getTexturePath(name) != "");
360 m_source_image_existence.set(name, is_known);
364 // Processes queued texture requests from other threads.
365 // Shall be called from the main thread.
368 // Insert an image into the cache without touching the filesystem.
369 // Shall be called from the main thread.
370 void insertSourceImage(const std::string &name, video::IImage *img);
372 // Rebuild images and textures from the current set of source images
373 // Shall be called from the main thread.
374 void rebuildImagesAndTextures();
376 // Render a mesh to a texture.
377 // Returns NULL if render-to-texture failed.
378 // Shall be called from the main thread.
379 video::ITexture* generateTextureFromMesh(
380 const TextureFromMeshParams ¶ms);
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 /*! Generates an image from a full string like
405 * "stone.png^mineral_coal.png^[crack:1:0".
406 * Shall be called from the main thread.
407 * The returned Image should be dropped.
409 video::IImage* generateImage(const std::string &name);
411 // Thread-safe cache of what source images are known (true = known)
412 MutexedMap<std::string, bool> m_source_image_existence;
414 // A texture id is index in this array.
415 // The first position contains a NULL texture.
416 std::vector<TextureInfo> m_textureinfo_cache;
417 // Maps a texture name to an index in the former.
418 std::map<std::string, u32> m_name_to_id;
419 // The two former containers are behind this mutex
420 Mutex m_textureinfo_cache_mutex;
422 // Queued texture fetches (to be processed by the main thread)
423 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
425 // Textures that have been overwritten with other ones
426 // but can't be deleted because the ITexture* might still be used
427 std::vector<video::ITexture*> m_texture_trash;
429 // Maps image file names to loaded palettes.
430 UNORDERED_MAP<std::string, Palette> m_palettes;
432 // Cached settings needed for making textures from meshes
433 bool m_setting_trilinear_filter;
434 bool m_setting_bilinear_filter;
435 bool m_setting_anisotropic_filter;
438 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
440 return new TextureSource(device);
443 TextureSource::TextureSource(IrrlichtDevice *device):
446 assert(m_device); // Pre-condition
448 m_main_thread = thr_get_current_thread_id();
450 // Add a NULL TextureInfo as the first index, named ""
451 m_textureinfo_cache.push_back(TextureInfo(""));
452 m_name_to_id[""] = 0;
454 // Cache some settings
455 // Note: Since this is only done once, the game must be restarted
456 // for these settings to take effect
457 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
458 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
459 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
462 TextureSource::~TextureSource()
464 video::IVideoDriver* driver = m_device->getVideoDriver();
466 unsigned int textures_before = driver->getTextureCount();
468 for (std::vector<TextureInfo>::iterator iter =
469 m_textureinfo_cache.begin();
470 iter != m_textureinfo_cache.end(); ++iter)
474 driver->removeTexture(iter->texture);
476 m_textureinfo_cache.clear();
478 for (std::vector<video::ITexture*>::iterator iter =
479 m_texture_trash.begin(); iter != m_texture_trash.end();
481 video::ITexture *t = *iter;
483 //cleanup trashed texture
484 driver->removeTexture(t);
487 infostream << "~TextureSource() "<< textures_before << "/"
488 << driver->getTextureCount() << std::endl;
491 u32 TextureSource::getTextureId(const std::string &name)
493 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
497 See if texture already exists
499 MutexAutoLock lock(m_textureinfo_cache_mutex);
500 std::map<std::string, u32>::iterator n;
501 n = m_name_to_id.find(name);
502 if (n != m_name_to_id.end())
511 if (thr_is_current_thread(m_main_thread))
513 return generateTexture(name);
517 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
519 // We're gonna ask the result to be put into here
520 static ResultQueue<std::string, u32, u8, u8> result_queue;
522 // Throw a request in
523 m_get_texture_queue.add(name, 0, 0, &result_queue);
525 /*infostream<<"Waiting for texture from main thread, name=\""
526 <<name<<"\""<<std::endl;*/
531 // Wait result for a second
532 GetResult<std::string, u32, u8, u8>
533 result = result_queue.pop_front(1000);
535 if (result.key == name) {
540 catch(ItemNotFoundException &e)
542 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
547 infostream<<"getTextureId(): Failed"<<std::endl;
552 // Draw an image on top of an another one, using the alpha channel of the
554 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
555 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
557 // Like blit_with_alpha, but only modifies destination pixels that
559 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
560 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
562 // Apply a color to an image. Uses an int (0-255) to calculate the ratio.
563 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
564 // color alpha with the destination alpha.
565 // Otherwise, any pixels that are not fully transparent get the color alpha.
566 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
567 const video::SColor &color, int ratio, bool keep_alpha);
569 // paint a texture using the given color
570 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
571 const video::SColor &color);
573 // Apply a mask to an image
574 static void apply_mask(video::IImage *mask, video::IImage *dst,
575 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
577 // Draw or overlay a crack
578 static void draw_crack(video::IImage *crack, video::IImage *dst,
579 bool use_overlay, s32 frame_count, s32 progression,
580 video::IVideoDriver *driver);
583 void brighten(video::IImage *image);
584 // Parse a transform name
585 u32 parseImageTransform(const std::string& s);
586 // Apply transform to image dimension
587 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
588 // Apply transform to image data
589 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
592 This method generates all the textures
594 u32 TextureSource::generateTexture(const std::string &name)
596 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
598 // Empty name means texture 0
600 infostream<<"generateTexture(): name is empty"<<std::endl;
606 See if texture already exists
608 MutexAutoLock lock(m_textureinfo_cache_mutex);
609 std::map<std::string, u32>::iterator n;
610 n = m_name_to_id.find(name);
611 if (n != m_name_to_id.end()) {
617 Calling only allowed from main thread
619 if (!thr_is_current_thread(m_main_thread)) {
620 errorstream<<"TextureSource::generateTexture() "
621 "called not from main thread"<<std::endl;
625 video::IVideoDriver *driver = m_device->getVideoDriver();
626 sanity_check(driver);
628 video::IImage *img = generateImage(name);
630 video::ITexture *tex = NULL;
634 img = Align2Npot2(img, driver);
636 // Create texture from resulting image
637 tex = driver->addTexture(name.c_str(), img);
638 guiScalingCache(io::path(name.c_str()), driver, img);
643 Add texture to caches (add NULL textures too)
646 MutexAutoLock lock(m_textureinfo_cache_mutex);
648 u32 id = m_textureinfo_cache.size();
649 TextureInfo ti(name, tex);
650 m_textureinfo_cache.push_back(ti);
651 m_name_to_id[name] = id;
656 std::string TextureSource::getTextureName(u32 id)
658 MutexAutoLock lock(m_textureinfo_cache_mutex);
660 if (id >= m_textureinfo_cache.size())
662 errorstream<<"TextureSource::getTextureName(): id="<<id
663 <<" >= m_textureinfo_cache.size()="
664 <<m_textureinfo_cache.size()<<std::endl;
668 return m_textureinfo_cache[id].name;
671 video::ITexture* TextureSource::getTexture(u32 id)
673 MutexAutoLock lock(m_textureinfo_cache_mutex);
675 if (id >= m_textureinfo_cache.size())
678 return m_textureinfo_cache[id].texture;
681 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
683 u32 actual_id = getTextureId(name);
687 return getTexture(actual_id);
690 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
692 return getTexture(name + "^[applyfiltersformesh", id);
695 Palette* TextureSource::getPalette(const std::string &name)
697 // Only the main thread may load images
698 sanity_check(thr_is_current_thread(m_main_thread));
703 UNORDERED_MAP<std::string, Palette>::iterator it = m_palettes.find(name);
704 if (it == m_palettes.end()) {
706 video::IImage *img = generateImage(name);
708 warningstream << "TextureSource::getPalette(): palette \"" << name
709 << "\" could not be loaded." << std::endl;
713 u32 w = img->getDimension().Width;
714 u32 h = img->getDimension().Height;
715 // Real area of the image
720 warningstream << "TextureSource::getPalette(): the specified"
721 << " palette image \"" << name << "\" is larger than 256"
722 << " pixels, using the first 256." << std::endl;
724 } else if (256 % area != 0)
725 warningstream << "TextureSource::getPalette(): the "
726 << "specified palette image \"" << name << "\" does not "
727 << "contain power of two pixels." << std::endl;
728 // We stretch the palette so it will fit 256 values
729 // This many param2 values will have the same color
730 u32 step = 256 / area;
731 // For each pixel in the image
732 for (u32 i = 0; i < area; i++) {
733 video::SColor c = img->getPixel(i % w, i / w);
734 // Fill in palette with 'step' colors
735 for (u32 j = 0; j < step; j++)
736 new_palette.push_back(c);
739 // Fill in remaining elements
740 while (new_palette.size() < 256)
741 new_palette.push_back(video::SColor(0xFFFFFFFF));
742 m_palettes[name] = new_palette;
743 it = m_palettes.find(name);
745 if (it != m_palettes.end())
746 return &((*it).second);
750 void TextureSource::processQueue()
755 //NOTE this is only thread safe for ONE consumer thread!
756 if (!m_get_texture_queue.empty())
758 GetRequest<std::string, u32, u8, u8>
759 request = m_get_texture_queue.pop();
761 /*infostream<<"TextureSource::processQueue(): "
762 <<"got texture request with "
763 <<"name=\""<<request.key<<"\""
766 m_get_texture_queue.pushResult(request, generateTexture(request.key));
770 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
772 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
774 sanity_check(thr_is_current_thread(m_main_thread));
776 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
777 m_source_image_existence.set(name, true);
780 void TextureSource::rebuildImagesAndTextures()
782 MutexAutoLock lock(m_textureinfo_cache_mutex);
784 video::IVideoDriver* driver = m_device->getVideoDriver();
785 sanity_check(driver);
788 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
789 TextureInfo *ti = &m_textureinfo_cache[i];
790 video::IImage *img = generateImage(ti->name);
792 img = Align2Npot2(img, driver);
794 // Create texture from resulting image
795 video::ITexture *t = NULL;
797 t = driver->addTexture(ti->name.c_str(), img);
798 guiScalingCache(io::path(ti->name.c_str()), driver, img);
801 video::ITexture *t_old = ti->texture;
806 m_texture_trash.push_back(t_old);
810 video::ITexture* TextureSource::generateTextureFromMesh(
811 const TextureFromMeshParams ¶ms)
813 video::IVideoDriver *driver = m_device->getVideoDriver();
814 sanity_check(driver);
817 const GLubyte* renderstr = glGetString(GL_RENDERER);
818 std::string renderer((char*) renderstr);
820 // use no render to texture hack
822 (renderer.find("Adreno") != std::string::npos) ||
823 (renderer.find("Mali") != std::string::npos) ||
824 (renderer.find("Immersion") != std::string::npos) ||
825 (renderer.find("Tegra") != std::string::npos) ||
826 g_settings->getBool("inventory_image_hack")
828 // Get a scene manager
829 scene::ISceneManager *smgr_main = m_device->getSceneManager();
830 sanity_check(smgr_main);
831 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
834 const float scaling = 0.2;
836 scene::IMeshSceneNode* meshnode =
837 smgr->addMeshSceneNode(params.mesh, NULL,
838 -1, v3f(0,0,0), v3f(0,0,0),
839 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
840 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
841 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
842 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
843 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
844 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
846 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
847 params.camera_position, params.camera_lookat);
848 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
849 camera->setProjectionMatrix(params.camera_projection_matrix, false);
851 smgr->setAmbientLight(params.ambient_light);
852 smgr->addLightSceneNode(0,
853 params.light_position,
855 params.light_radius*scaling);
857 core::dimension2d<u32> screen = driver->getScreenSize();
860 driver->beginScene(true, true, video::SColor(0,0,0,0));
861 driver->clearZBuffer();
864 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
866 irr::video::IImage* rawImage =
867 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
869 u8* pixels = static_cast<u8*>(rawImage->lock());
876 core::rect<s32> source(
877 screen.Width /2 - (screen.Width * (scaling / 2)),
878 screen.Height/2 - (screen.Height * (scaling / 2)),
879 screen.Width /2 + (screen.Width * (scaling / 2)),
880 screen.Height/2 + (screen.Height * (scaling / 2))
883 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
884 partsize.Width, partsize.Height, GL_RGBA,
885 GL_UNSIGNED_BYTE, pixels);
889 // Drop scene manager
892 unsigned int pixelcount = partsize.Width*partsize.Height;
895 for (unsigned int i=0; i < pixelcount; i++) {
913 video::IImage* inventory_image =
914 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
916 rawImage->copyToScaling(inventory_image);
919 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
921 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
922 inventory_image->drop();
925 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
929 driver->makeColorKeyTexture(rtt, v2s32(0,0));
931 if (params.delete_texture_on_shutdown)
932 m_texture_trash.push_back(rtt);
938 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
940 static bool warned = false;
943 errorstream<<"TextureSource::generateTextureFromMesh(): "
944 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
950 // Create render target texture
951 video::ITexture *rtt = driver->addRenderTargetTexture(
952 params.dim, params.rtt_texture_name.c_str(),
953 video::ECF_A8R8G8B8);
956 errorstream<<"TextureSource::generateTextureFromMesh(): "
957 <<"addRenderTargetTexture returned NULL."<<std::endl;
962 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
963 driver->removeTexture(rtt);
964 errorstream<<"TextureSource::generateTextureFromMesh(): "
965 <<"failed to set render target"<<std::endl;
969 // Get a scene manager
970 scene::ISceneManager *smgr_main = m_device->getSceneManager();
972 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
975 scene::IMeshSceneNode* meshnode =
976 smgr->addMeshSceneNode(params.mesh, NULL,
977 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
978 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
979 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
980 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
981 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
982 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
984 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
985 params.camera_position, params.camera_lookat);
986 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
987 camera->setProjectionMatrix(params.camera_projection_matrix, false);
989 smgr->setAmbientLight(params.ambient_light);
990 smgr->addLightSceneNode(0,
991 params.light_position,
993 params.light_radius);
996 driver->beginScene(true, true, video::SColor(0,0,0,0));
1000 // Drop scene manager
1003 // Unset render target
1004 driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
1006 if (params.delete_texture_on_shutdown)
1007 m_texture_trash.push_back(rtt);
1012 video::IImage* TextureSource::generateImage(const std::string &name)
1014 // Get the base image
1016 const char separator = '^';
1017 const char escape = '\\';
1018 const char paren_open = '(';
1019 const char paren_close = ')';
1021 // Find last separator in the name
1022 s32 last_separator_pos = -1;
1024 for (s32 i = name.size() - 1; i >= 0; i--) {
1025 if (i > 0 && name[i-1] == escape)
1029 if (paren_bal == 0) {
1030 last_separator_pos = i;
1031 i = -1; // break out of loop
1035 if (paren_bal == 0) {
1036 errorstream << "generateImage(): unbalanced parentheses"
1037 << "(extranous '(') while generating texture \""
1038 << name << "\"" << std::endl;
1050 if (paren_bal > 0) {
1051 errorstream << "generateImage(): unbalanced parentheses"
1052 << "(missing matching '(') while generating texture \""
1053 << name << "\"" << std::endl;
1058 video::IImage *baseimg = NULL;
1061 If separator was found, make the base image
1062 using a recursive call.
1064 if (last_separator_pos != -1) {
1065 baseimg = generateImage(name.substr(0, last_separator_pos));
1069 video::IVideoDriver* driver = m_device->getVideoDriver();
1070 sanity_check(driver);
1073 Parse out the last part of the name of the image and act
1077 std::string last_part_of_name = name.substr(last_separator_pos + 1);
1080 If this name is enclosed in parentheses, generate it
1081 and blit it onto the base image
1083 if (last_part_of_name[0] == paren_open
1084 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1085 std::string name2 = last_part_of_name.substr(1,
1086 last_part_of_name.size() - 2);
1087 video::IImage *tmp = generateImage(name2);
1089 errorstream << "generateImage(): "
1090 "Failed to generate \"" << name2 << "\""
1094 core::dimension2d<u32> dim = tmp->getDimension();
1096 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1101 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1102 // Generate image according to part of name
1103 errorstream << "generateImage(): "
1104 "Failed to generate \"" << last_part_of_name << "\""
1108 // If no resulting image, print a warning
1109 if (baseimg == NULL) {
1110 errorstream << "generateImage(): baseimg is NULL (attempted to"
1111 " create texture \"" << name << "\")" << std::endl;
1118 #include <GLES/gl.h>
1120 * Check and align image to npot2 if required by hardware
1121 * @param image image to check for npot2 alignment
1122 * @param driver driver to use for image operations
1123 * @return image or copy of image aligned to npot2
1126 inline u16 get_GL_major_version()
1128 const GLubyte *gl_version = glGetString(GL_VERSION);
1129 return (u16) (gl_version[0] - '0');
1132 video::IImage * Align2Npot2(video::IImage * image,
1133 video::IVideoDriver* driver)
1135 if (image == NULL) {
1139 core::dimension2d<u32> dim = image->getDimension();
1141 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1143 // Only GLES2 is trusted to correctly report npot support
1144 if (get_GL_major_version() > 1 &&
1145 extensions.find("GL_OES_texture_npot") != std::string::npos) {
1149 unsigned int height = npot2(dim.Height);
1150 unsigned int width = npot2(dim.Width);
1152 if ((dim.Height == height) &&
1153 (dim.Width == width)) {
1157 if (dim.Height > height) {
1161 if (dim.Width > width) {
1165 video::IImage *targetimage =
1166 driver->createImage(video::ECF_A8R8G8B8,
1167 core::dimension2d<u32>(width, height));
1169 if (targetimage != NULL) {
1170 image->copyToScaling(targetimage);
1178 static std::string unescape_string(const std::string &str, const char esc = '\\')
1181 size_t pos = 0, cpos;
1182 out.reserve(str.size());
1184 cpos = str.find_first_of(esc, pos);
1185 if (cpos == std::string::npos) {
1186 out += str.substr(pos);
1189 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1195 bool TextureSource::generateImagePart(std::string part_of_name,
1196 video::IImage *& baseimg)
1198 const char escape = '\\'; // same as in generateImage()
1199 video::IVideoDriver* driver = m_device->getVideoDriver();
1200 sanity_check(driver);
1202 // Stuff starting with [ are special commands
1203 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1205 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1207 image = Align2Npot2(image, driver);
1209 if (image == NULL) {
1210 if (part_of_name != "") {
1211 if (part_of_name.find("_normal.png") == std::string::npos){
1212 errorstream<<"generateImage(): Could not load image \""
1213 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1214 errorstream<<"generateImage(): Creating a dummy"
1215 <<" image for \""<<part_of_name<<"\""<<std::endl;
1217 infostream<<"generateImage(): Could not load normal map \""
1218 <<part_of_name<<"\""<<std::endl;
1219 infostream<<"generateImage(): Creating a dummy"
1220 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1224 // Just create a dummy image
1225 //core::dimension2d<u32> dim(2,2);
1226 core::dimension2d<u32> dim(1,1);
1227 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1228 sanity_check(image != NULL);
1229 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1230 image->setPixel(1,0, video::SColor(255,0,255,0));
1231 image->setPixel(0,1, video::SColor(255,0,0,255));
1232 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1233 image->setPixel(0,0, video::SColor(255,myrand()%256,
1234 myrand()%256,myrand()%256));
1235 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1236 myrand()%256,myrand()%256));
1237 image->setPixel(0,1, video::SColor(255,myrand()%256,
1238 myrand()%256,myrand()%256));
1239 image->setPixel(1,1, video::SColor(255,myrand()%256,
1240 myrand()%256,myrand()%256));*/
1243 // If base image is NULL, load as base.
1244 if (baseimg == NULL)
1246 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1248 Copy it this way to get an alpha channel.
1249 Otherwise images with alpha cannot be blitted on
1250 images that don't have alpha in the original file.
1252 core::dimension2d<u32> dim = image->getDimension();
1253 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1254 image->copyTo(baseimg);
1256 // Else blit on base.
1259 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1260 // Size of the copied area
1261 core::dimension2d<u32> dim = image->getDimension();
1262 //core::dimension2d<u32> dim(16,16);
1263 // Position to copy the blitted to in the base image
1264 core::position2d<s32> pos_to(0,0);
1265 // Position to copy the blitted from in the blitted image
1266 core::position2d<s32> pos_from(0,0);
1268 /*image->copyToWithAlpha(baseimg, pos_to,
1269 core::rect<s32>(pos_from, dim),
1270 video::SColor(255,255,255,255),
1273 core::dimension2d<u32> dim_dst = baseimg->getDimension();
1274 if (dim == dim_dst) {
1275 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1276 } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1277 // Upscale overlying image
1278 video::IImage* scaled_image = m_device->getVideoDriver()->
1279 createImage(video::ECF_A8R8G8B8, dim_dst);
1280 image->copyToScaling(scaled_image);
1282 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1283 scaled_image->drop();
1285 // Upscale base image
1286 video::IImage* scaled_base = m_device->getVideoDriver()->
1287 createImage(video::ECF_A8R8G8B8, dim);
1288 baseimg->copyToScaling(scaled_base);
1290 baseimg = scaled_base;
1292 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1300 // A special texture modification
1302 /*infostream<<"generateImage(): generating special "
1303 <<"modification \""<<part_of_name<<"\""
1309 Adds a cracking texture
1310 N = animation frame count, P = crack progression
1312 if (str_starts_with(part_of_name, "[crack"))
1314 if (baseimg == NULL) {
1315 errorstream<<"generateImagePart(): baseimg == NULL "
1316 <<"for part_of_name=\""<<part_of_name
1317 <<"\", cancelling."<<std::endl;
1321 // Crack image number and overlay option
1322 bool use_overlay = (part_of_name[6] == 'o');
1323 Strfnd sf(part_of_name);
1325 s32 frame_count = stoi(sf.next(":"));
1326 s32 progression = stoi(sf.next(":"));
1328 if (progression >= 0) {
1332 It is an image with a number of cracking stages
1335 video::IImage *img_crack = m_sourcecache.getOrLoad(
1336 "crack_anylength.png", m_device);
1339 draw_crack(img_crack, baseimg,
1340 use_overlay, frame_count,
1341 progression, driver);
1347 [combine:WxH:X,Y=filename:X,Y=filename2
1348 Creates a bigger texture from any amount of smaller ones
1350 else if (str_starts_with(part_of_name, "[combine"))
1352 Strfnd sf(part_of_name);
1354 u32 w0 = stoi(sf.next("x"));
1355 u32 h0 = stoi(sf.next(":"));
1356 core::dimension2d<u32> dim(w0,h0);
1357 if (baseimg == NULL) {
1358 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1359 baseimg->fill(video::SColor(0,0,0,0));
1361 while (sf.at_end() == false) {
1362 u32 x = stoi(sf.next(","));
1363 u32 y = stoi(sf.next("="));
1364 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1365 infostream<<"Adding \""<<filename
1366 <<"\" to combined ("<<x<<","<<y<<")"
1368 video::IImage *img = generateImage(filename);
1370 core::dimension2d<u32> dim = img->getDimension();
1371 infostream<<"Size "<<dim.Width
1372 <<"x"<<dim.Height<<std::endl;
1373 core::position2d<s32> pos_base(x, y);
1374 video::IImage *img2 =
1375 driver->createImage(video::ECF_A8R8G8B8, dim);
1378 /*img2->copyToWithAlpha(baseimg, pos_base,
1379 core::rect<s32>(v2s32(0,0), dim),
1380 video::SColor(255,255,255,255),
1382 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1385 errorstream << "generateImagePart(): Failed to load image \""
1386 << filename << "\" for [combine" << std::endl;
1393 else if (str_starts_with(part_of_name, "[brighten"))
1395 if (baseimg == NULL) {
1396 errorstream<<"generateImagePart(): baseimg==NULL "
1397 <<"for part_of_name=\""<<part_of_name
1398 <<"\", cancelling."<<std::endl;
1406 Make image completely opaque.
1407 Used for the leaves texture when in old leaves mode, so
1408 that the transparent parts don't look completely black
1409 when simple alpha channel is used for rendering.
1411 else if (str_starts_with(part_of_name, "[noalpha"))
1413 if (baseimg == NULL){
1414 errorstream<<"generateImagePart(): baseimg==NULL "
1415 <<"for part_of_name=\""<<part_of_name
1416 <<"\", cancelling."<<std::endl;
1420 core::dimension2d<u32> dim = baseimg->getDimension();
1422 // Set alpha to full
1423 for (u32 y=0; y<dim.Height; y++)
1424 for (u32 x=0; x<dim.Width; x++)
1426 video::SColor c = baseimg->getPixel(x,y);
1428 baseimg->setPixel(x,y,c);
1433 Convert one color to transparent.
1435 else if (str_starts_with(part_of_name, "[makealpha:"))
1437 if (baseimg == NULL) {
1438 errorstream<<"generateImagePart(): baseimg == NULL "
1439 <<"for part_of_name=\""<<part_of_name
1440 <<"\", cancelling."<<std::endl;
1444 Strfnd sf(part_of_name.substr(11));
1445 u32 r1 = stoi(sf.next(","));
1446 u32 g1 = stoi(sf.next(","));
1447 u32 b1 = stoi(sf.next(""));
1449 core::dimension2d<u32> dim = baseimg->getDimension();
1451 /*video::IImage *oldbaseimg = baseimg;
1452 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1453 oldbaseimg->copyTo(baseimg);
1454 oldbaseimg->drop();*/
1456 // Set alpha to full
1457 for (u32 y=0; y<dim.Height; y++)
1458 for (u32 x=0; x<dim.Width; x++)
1460 video::SColor c = baseimg->getPixel(x,y);
1462 u32 g = c.getGreen();
1463 u32 b = c.getBlue();
1464 if (!(r == r1 && g == g1 && b == b1))
1467 baseimg->setPixel(x,y,c);
1472 Rotates and/or flips the image.
1474 N can be a number (between 0 and 7) or a transform name.
1475 Rotations are counter-clockwise.
1477 1 R90 rotate by 90 degrees
1478 2 R180 rotate by 180 degrees
1479 3 R270 rotate by 270 degrees
1481 5 FXR90 flip X then rotate by 90 degrees
1483 7 FYR90 flip Y then rotate by 90 degrees
1485 Note: Transform names can be concatenated to produce
1486 their product (applies the first then the second).
1487 The resulting transform will be equivalent to one of the
1488 eight existing ones, though (see: dihedral group).
1490 else if (str_starts_with(part_of_name, "[transform"))
1492 if (baseimg == NULL) {
1493 errorstream<<"generateImagePart(): baseimg == NULL "
1494 <<"for part_of_name=\""<<part_of_name
1495 <<"\", cancelling."<<std::endl;
1499 u32 transform = parseImageTransform(part_of_name.substr(10));
1500 core::dimension2d<u32> dim = imageTransformDimension(
1501 transform, baseimg->getDimension());
1502 video::IImage *image = driver->createImage(
1503 baseimg->getColorFormat(), dim);
1504 sanity_check(image != NULL);
1505 imageTransform(transform, baseimg, image);
1510 [inventorycube{topimage{leftimage{rightimage
1511 In every subimage, replace ^ with &.
1512 Create an "inventory cube".
1513 NOTE: This should be used only on its own.
1514 Example (a grass block (not actually used in game):
1515 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1517 else if (str_starts_with(part_of_name, "[inventorycube"))
1519 if (baseimg != NULL){
1520 errorstream<<"generateImagePart(): baseimg != NULL "
1521 <<"for part_of_name=\""<<part_of_name
1522 <<"\", cancelling."<<std::endl;
1526 str_replace(part_of_name, '&', '^');
1527 Strfnd sf(part_of_name);
1529 std::string imagename_top = sf.next("{");
1530 std::string imagename_left = sf.next("{");
1531 std::string imagename_right = sf.next("{");
1533 // Generate images for the faces of the cube
1534 video::IImage *img_top = generateImage(imagename_top);
1535 video::IImage *img_left = generateImage(imagename_left);
1536 video::IImage *img_right = generateImage(imagename_right);
1538 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1539 errorstream << "generateImagePart(): Failed to create textures"
1540 << " for inventorycube \"" << part_of_name << "\""
1542 baseimg = generateImage(imagename_top);
1547 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1548 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1550 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1551 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1553 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1554 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1557 // Create textures from images
1558 video::ITexture *texture_top = driver->addTexture(
1559 (imagename_top + "__temp__").c_str(), img_top);
1560 video::ITexture *texture_left = driver->addTexture(
1561 (imagename_left + "__temp__").c_str(), img_left);
1562 video::ITexture *texture_right = driver->addTexture(
1563 (imagename_right + "__temp__").c_str(), img_right);
1564 FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1572 Draw a cube mesh into a render target texture
1574 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1575 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1576 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1577 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1578 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1579 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1580 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1581 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1583 TextureFromMeshParams params;
1585 params.dim.set(64, 64);
1586 params.rtt_texture_name = part_of_name + "_RTT";
1587 // We will delete the rtt texture ourselves
1588 params.delete_texture_on_shutdown = false;
1589 params.camera_position.set(0, 1.0, -1.5);
1590 params.camera_position.rotateXZBy(45);
1591 params.camera_lookat.set(0, 0, 0);
1592 // Set orthogonal projection
1593 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1594 1.65, 1.65, 0, 100);
1596 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1597 params.light_position.set(10, 100, -50);
1598 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1599 params.light_radius = 1000;
1601 video::ITexture *rtt = generateTextureFromMesh(params);
1607 driver->removeTexture(texture_top);
1608 driver->removeTexture(texture_left);
1609 driver->removeTexture(texture_right);
1612 baseimg = generateImage(imagename_top);
1616 // Create image of render target
1617 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1618 FATAL_ERROR_IF(!image, "Could not create image of render target");
1621 driver->removeTexture(rtt);
1623 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1626 image->copyTo(baseimg);
1631 [lowpart:percent:filename
1632 Adds the lower part of a texture
1634 else if (str_starts_with(part_of_name, "[lowpart:"))
1636 Strfnd sf(part_of_name);
1638 u32 percent = stoi(sf.next(":"));
1639 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1641 if (baseimg == NULL)
1642 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1643 video::IImage *img = generateImage(filename);
1646 core::dimension2d<u32> dim = img->getDimension();
1647 core::position2d<s32> pos_base(0, 0);
1648 video::IImage *img2 =
1649 driver->createImage(video::ECF_A8R8G8B8, dim);
1652 core::position2d<s32> clippos(0, 0);
1653 clippos.Y = dim.Height * (100-percent) / 100;
1654 core::dimension2d<u32> clipdim = dim;
1655 clipdim.Height = clipdim.Height * percent / 100 + 1;
1656 core::rect<s32> cliprect(clippos, clipdim);
1657 img2->copyToWithAlpha(baseimg, pos_base,
1658 core::rect<s32>(v2s32(0,0), dim),
1659 video::SColor(255,255,255,255),
1666 Crops a frame of a vertical animation.
1667 N = frame count, I = frame index
1669 else if (str_starts_with(part_of_name, "[verticalframe:"))
1671 Strfnd sf(part_of_name);
1673 u32 frame_count = stoi(sf.next(":"));
1674 u32 frame_index = stoi(sf.next(":"));
1676 if (baseimg == NULL){
1677 errorstream<<"generateImagePart(): baseimg != NULL "
1678 <<"for part_of_name=\""<<part_of_name
1679 <<"\", cancelling."<<std::endl;
1683 v2u32 frame_size = baseimg->getDimension();
1684 frame_size.Y /= frame_count;
1686 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1689 errorstream<<"generateImagePart(): Could not create image "
1690 <<"for part_of_name=\""<<part_of_name
1691 <<"\", cancelling."<<std::endl;
1695 // Fill target image with transparency
1696 img->fill(video::SColor(0,0,0,0));
1698 core::dimension2d<u32> dim = frame_size;
1699 core::position2d<s32> pos_dst(0, 0);
1700 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1701 baseimg->copyToWithAlpha(img, pos_dst,
1702 core::rect<s32>(pos_src, dim),
1703 video::SColor(255,255,255,255),
1711 Applies a mask to an image
1713 else if (str_starts_with(part_of_name, "[mask:"))
1715 if (baseimg == NULL) {
1716 errorstream << "generateImage(): baseimg == NULL "
1717 << "for part_of_name=\"" << part_of_name
1718 << "\", cancelling." << std::endl;
1721 Strfnd sf(part_of_name);
1723 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1725 video::IImage *img = generateImage(filename);
1727 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1728 img->getDimension());
1731 errorstream << "generateImage(): Failed to load \""
1732 << filename << "\".";
1737 multiplys a given color to any pixel of an image
1738 color = color as ColorString
1740 else if (str_starts_with(part_of_name, "[multiply:")) {
1741 Strfnd sf(part_of_name);
1743 std::string color_str = sf.next(":");
1745 if (baseimg == NULL) {
1746 errorstream << "generateImagePart(): baseimg != NULL "
1747 << "for part_of_name=\"" << part_of_name
1748 << "\", cancelling." << std::endl;
1752 video::SColor color;
1754 if (!parseColorString(color_str, color, false))
1757 apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1761 Overlays image with given color
1762 color = color as ColorString
1764 else if (str_starts_with(part_of_name, "[colorize:"))
1766 Strfnd sf(part_of_name);
1768 std::string color_str = sf.next(":");
1769 std::string ratio_str = sf.next(":");
1771 if (baseimg == NULL) {
1772 errorstream << "generateImagePart(): baseimg != NULL "
1773 << "for part_of_name=\"" << part_of_name
1774 << "\", cancelling." << std::endl;
1778 video::SColor color;
1780 bool keep_alpha = false;
1782 if (!parseColorString(color_str, color, false))
1785 if (is_number(ratio_str))
1786 ratio = mystoi(ratio_str, 0, 255);
1787 else if (ratio_str == "alpha")
1790 apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1793 [applyfiltersformesh
1796 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1798 // Apply the "clean transparent" filter, if configured.
1799 if (g_settings->getBool("texture_clean_transparent"))
1800 imageCleanTransparent(baseimg, 127);
1802 /* Upscale textures to user's requested minimum size. This is a trick to make
1803 * filters look as good on low-res textures as on high-res ones, by making
1804 * low-res textures BECOME high-res ones. This is helpful for worlds that
1805 * mix high- and low-res textures, or for mods with least-common-denominator
1806 * textures that don't have the resources to offer high-res alternatives.
1808 s32 scaleto = g_settings->getS32("texture_min_size");
1810 const core::dimension2d<u32> dim = baseimg->getDimension();
1812 /* Calculate scaling needed to make the shortest texture dimension
1813 * equal to the target minimum. If e.g. this is a vertical frames
1814 * animation, the short dimension will be the real size.
1816 if ((dim.Width == 0) || (dim.Height == 0)) {
1817 errorstream << "generateImagePart(): Illegal 0 dimension "
1818 << "for part_of_name=\""<< part_of_name
1819 << "\", cancelling." << std::endl;
1822 u32 xscale = scaleto / dim.Width;
1823 u32 yscale = scaleto / dim.Height;
1824 u32 scale = (xscale > yscale) ? xscale : yscale;
1826 // Never downscale; only scale up by 2x or more.
1828 u32 w = scale * dim.Width;
1829 u32 h = scale * dim.Height;
1830 const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1831 video::IImage *newimg = driver->createImage(
1832 baseimg->getColorFormat(), newdim);
1833 baseimg->copyToScaling(newimg);
1841 Resizes the base image to the given dimensions
1843 else if (str_starts_with(part_of_name, "[resize"))
1845 if (baseimg == NULL) {
1846 errorstream << "generateImagePart(): baseimg == NULL "
1847 << "for part_of_name=\""<< part_of_name
1848 << "\", cancelling." << std::endl;
1852 Strfnd sf(part_of_name);
1854 u32 width = stoi(sf.next("x"));
1855 u32 height = stoi(sf.next(""));
1856 core::dimension2d<u32> dim(width, height);
1858 video::IImage* image = m_device->getVideoDriver()->
1859 createImage(video::ECF_A8R8G8B8, dim);
1860 baseimg->copyToScaling(image);
1866 Makes the base image transparent according to the given ratio.
1867 R must be between 0 and 255.
1868 0 means totally transparent.
1869 255 means totally opaque.
1871 else if (str_starts_with(part_of_name, "[opacity:")) {
1872 if (baseimg == NULL) {
1873 errorstream << "generateImagePart(): baseimg == NULL "
1874 << "for part_of_name=\"" << part_of_name
1875 << "\", cancelling." << std::endl;
1879 Strfnd sf(part_of_name);
1882 u32 ratio = mystoi(sf.next(""), 0, 255);
1884 core::dimension2d<u32> dim = baseimg->getDimension();
1886 for (u32 y = 0; y < dim.Height; y++)
1887 for (u32 x = 0; x < dim.Width; x++)
1889 video::SColor c = baseimg->getPixel(x, y);
1890 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1891 baseimg->setPixel(x, y, c);
1896 Inverts the given channels of the base image.
1897 Mode may contain the characters "r", "g", "b", "a".
1898 Only the channels that are mentioned in the mode string
1901 else if (str_starts_with(part_of_name, "[invert:")) {
1902 if (baseimg == NULL) {
1903 errorstream << "generateImagePart(): baseimg == NULL "
1904 << "for part_of_name=\"" << part_of_name
1905 << "\", cancelling." << std::endl;
1909 Strfnd sf(part_of_name);
1912 std::string mode = sf.next("");
1914 if (mode.find("a") != std::string::npos)
1915 mask |= 0xff000000UL;
1916 if (mode.find("r") != std::string::npos)
1917 mask |= 0x00ff0000UL;
1918 if (mode.find("g") != std::string::npos)
1919 mask |= 0x0000ff00UL;
1920 if (mode.find("b") != std::string::npos)
1921 mask |= 0x000000ffUL;
1923 core::dimension2d<u32> dim = baseimg->getDimension();
1925 for (u32 y = 0; y < dim.Height; y++)
1926 for (u32 x = 0; x < dim.Width; x++)
1928 video::SColor c = baseimg->getPixel(x, y);
1930 baseimg->setPixel(x, y, c);
1935 Retrieves a tile at position X,Y (in tiles)
1936 from the base image it assumes to be a
1937 tilesheet with dimensions W,H (in tiles).
1939 else if (part_of_name.substr(0,7) == "[sheet:") {
1940 if (baseimg == NULL) {
1941 errorstream << "generateImagePart(): baseimg != NULL "
1942 << "for part_of_name=\"" << part_of_name
1943 << "\", cancelling." << std::endl;
1947 Strfnd sf(part_of_name);
1949 u32 w0 = stoi(sf.next("x"));
1950 u32 h0 = stoi(sf.next(":"));
1951 u32 x0 = stoi(sf.next(","));
1952 u32 y0 = stoi(sf.next(":"));
1954 core::dimension2d<u32> img_dim = baseimg->getDimension();
1955 core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1957 video::IImage *img = driver->createImage(
1958 video::ECF_A8R8G8B8, tile_dim);
1960 errorstream << "generateImagePart(): Could not create image "
1961 << "for part_of_name=\"" << part_of_name
1962 << "\", cancelling." << std::endl;
1966 img->fill(video::SColor(0,0,0,0));
1967 v2u32 vdim(tile_dim);
1968 core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1969 baseimg->copyToWithAlpha(img, v2s32(0), rect,
1970 video::SColor(255,255,255,255), NULL);
1978 errorstream << "generateImagePart(): Invalid "
1979 " modification: \"" << part_of_name << "\"" << std::endl;
1987 Draw an image on top of an another one, using the alpha channel of the
1990 This exists because IImage::copyToWithAlpha() doesn't seem to always
1993 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1994 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1996 for (u32 y0=0; y0<size.Y; y0++)
1997 for (u32 x0=0; x0<size.X; x0++)
1999 s32 src_x = src_pos.X + x0;
2000 s32 src_y = src_pos.Y + y0;
2001 s32 dst_x = dst_pos.X + x0;
2002 s32 dst_y = dst_pos.Y + y0;
2003 video::SColor src_c = src->getPixel(src_x, src_y);
2004 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2005 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2006 dst->setPixel(dst_x, dst_y, dst_c);
2011 Draw an image on top of an another one, using the alpha channel of the
2012 source image; only modify fully opaque pixels in destinaion
2014 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
2015 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
2017 for (u32 y0=0; y0<size.Y; y0++)
2018 for (u32 x0=0; x0<size.X; x0++)
2020 s32 src_x = src_pos.X + x0;
2021 s32 src_y = src_pos.Y + y0;
2022 s32 dst_x = dst_pos.X + x0;
2023 s32 dst_y = dst_pos.Y + y0;
2024 video::SColor src_c = src->getPixel(src_x, src_y);
2025 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2026 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
2028 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2029 dst->setPixel(dst_x, dst_y, dst_c);
2034 // This function has been disabled because it is currently unused.
2035 // Feel free to re-enable if you find it handy.
2038 Draw an image on top of an another one, using the specified ratio
2039 modify all partially-opaque pixels in the destination.
2041 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
2042 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
2044 for (u32 y0 = 0; y0 < size.Y; y0++)
2045 for (u32 x0 = 0; x0 < size.X; x0++)
2047 s32 src_x = src_pos.X + x0;
2048 s32 src_y = src_pos.Y + y0;
2049 s32 dst_x = dst_pos.X + x0;
2050 s32 dst_y = dst_pos.Y + y0;
2051 video::SColor src_c = src->getPixel(src_x, src_y);
2052 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2053 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
2056 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
2058 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
2059 dst->setPixel(dst_x, dst_y, dst_c);
2066 Apply color to destination
2068 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2069 const video::SColor &color, int ratio, bool keep_alpha)
2071 u32 alpha = color.getAlpha();
2072 video::SColor dst_c;
2073 if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2074 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2076 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2077 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2078 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2079 if (dst_alpha > 0) {
2080 dst_c.setAlpha(dst_alpha * alpha / 255);
2081 dst->setPixel(x, y, dst_c);
2084 } else { // replace the color including the alpha
2085 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2086 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2087 if (dst->getPixel(x, y).getAlpha() > 0)
2088 dst->setPixel(x, y, color);
2090 } else { // interpolate between the color and destination
2091 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2092 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2093 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2094 dst_c = dst->getPixel(x, y);
2095 if (dst_c.getAlpha() > 0) {
2096 dst_c = color.getInterpolated(dst_c, interp);
2097 dst->setPixel(x, y, dst_c);
2104 Apply color to destination
2106 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2107 const video::SColor &color)
2109 video::SColor dst_c;
2111 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2112 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2113 dst_c = dst->getPixel(x, y);
2116 (dst_c.getRed() * color.getRed()) / 255,
2117 (dst_c.getGreen() * color.getGreen()) / 255,
2118 (dst_c.getBlue() * color.getBlue()) / 255
2120 dst->setPixel(x, y, dst_c);
2125 Apply mask to destination
2127 static void apply_mask(video::IImage *mask, video::IImage *dst,
2128 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2130 for (u32 y0 = 0; y0 < size.Y; y0++) {
2131 for (u32 x0 = 0; x0 < size.X; x0++) {
2132 s32 mask_x = x0 + mask_pos.X;
2133 s32 mask_y = y0 + mask_pos.Y;
2134 s32 dst_x = x0 + dst_pos.X;
2135 s32 dst_y = y0 + dst_pos.Y;
2136 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2137 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2138 dst_c.color &= mask_c.color;
2139 dst->setPixel(dst_x, dst_y, dst_c);
2144 static void draw_crack(video::IImage *crack, video::IImage *dst,
2145 bool use_overlay, s32 frame_count, s32 progression,
2146 video::IVideoDriver *driver)
2148 // Dimension of destination image
2149 core::dimension2d<u32> dim_dst = dst->getDimension();
2150 // Dimension of original image
2151 core::dimension2d<u32> dim_crack = crack->getDimension();
2152 // Count of crack stages
2153 s32 crack_count = dim_crack.Height / dim_crack.Width;
2154 // Limit frame_count
2155 if (frame_count > (s32) dim_dst.Height)
2156 frame_count = dim_dst.Height;
2157 if (frame_count < 1)
2159 // Limit progression
2160 if (progression > crack_count-1)
2161 progression = crack_count-1;
2162 // Dimension of a single crack stage
2163 core::dimension2d<u32> dim_crack_cropped(
2167 // Dimension of the scaled crack stage,
2168 // which is the same as the dimension of a single destination frame
2169 core::dimension2d<u32> dim_crack_scaled(
2171 dim_dst.Height / frame_count
2173 // Create cropped and scaled crack images
2174 video::IImage *crack_cropped = driver->createImage(
2175 video::ECF_A8R8G8B8, dim_crack_cropped);
2176 video::IImage *crack_scaled = driver->createImage(
2177 video::ECF_A8R8G8B8, dim_crack_scaled);
2179 if (crack_cropped && crack_scaled)
2182 v2s32 pos_crack(0, progression*dim_crack.Width);
2183 crack->copyTo(crack_cropped,
2185 core::rect<s32>(pos_crack, dim_crack_cropped));
2186 // Scale crack image by copying
2187 crack_cropped->copyToScaling(crack_scaled);
2188 // Copy or overlay crack image onto each frame
2189 for (s32 i = 0; i < frame_count; ++i)
2191 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2194 blit_with_alpha_overlay(crack_scaled, dst,
2195 v2s32(0,0), dst_pos,
2200 blit_with_alpha(crack_scaled, dst,
2201 v2s32(0,0), dst_pos,
2208 crack_scaled->drop();
2211 crack_cropped->drop();
2214 void brighten(video::IImage *image)
2219 core::dimension2d<u32> dim = image->getDimension();
2221 for (u32 y=0; y<dim.Height; y++)
2222 for (u32 x=0; x<dim.Width; x++)
2224 video::SColor c = image->getPixel(x,y);
2225 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2226 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2227 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2228 image->setPixel(x,y,c);
2232 u32 parseImageTransform(const std::string& s)
2234 int total_transform = 0;
2236 std::string transform_names[8];
2237 transform_names[0] = "i";
2238 transform_names[1] = "r90";
2239 transform_names[2] = "r180";
2240 transform_names[3] = "r270";
2241 transform_names[4] = "fx";
2242 transform_names[6] = "fy";
2244 std::size_t pos = 0;
2245 while(pos < s.size())
2248 for (int i = 0; i <= 7; ++i)
2250 const std::string &name_i = transform_names[i];
2252 if (s[pos] == ('0' + i))
2258 else if (!(name_i.empty()) &&
2259 lowercase(s.substr(pos, name_i.size())) == name_i)
2262 pos += name_i.size();
2269 // Multiply total_transform and transform in the group D4
2272 new_total = (transform + total_transform) % 4;
2274 new_total = (transform - total_transform + 8) % 4;
2275 if ((transform >= 4) ^ (total_transform >= 4))
2278 total_transform = new_total;
2280 return total_transform;
2283 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2285 if (transform % 2 == 0)
2288 return core::dimension2d<u32>(dim.Height, dim.Width);
2291 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2293 if (src == NULL || dst == NULL)
2296 core::dimension2d<u32> dstdim = dst->getDimension();
2299 assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2300 assert(transform <= 7);
2303 Compute the transformation from source coordinates (sx,sy)
2304 to destination coordinates (dx,dy).
2308 if (transform == 0) // identity
2309 sxn = 0, syn = 2; // sx = dx, sy = dy
2310 else if (transform == 1) // rotate by 90 degrees ccw
2311 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
2312 else if (transform == 2) // rotate by 180 degrees
2313 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
2314 else if (transform == 3) // rotate by 270 degrees ccw
2315 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
2316 else if (transform == 4) // flip x
2317 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
2318 else if (transform == 5) // flip x then rotate by 90 degrees ccw
2319 sxn = 2, syn = 0; // sx = dy, sy = dx
2320 else if (transform == 6) // flip y
2321 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
2322 else if (transform == 7) // flip y then rotate by 90 degrees ccw
2323 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
2325 for (u32 dy=0; dy<dstdim.Height; dy++)
2326 for (u32 dx=0; dx<dstdim.Width; dx++)
2328 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2329 u32 sx = entries[sxn];
2330 u32 sy = entries[syn];
2331 video::SColor c = src->getPixel(sx,sy);
2332 dst->setPixel(dx,dy,c);
2336 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2338 if (isKnownSourceImage("override_normal.png"))
2339 return getTexture("override_normal.png");
2340 std::string fname_base = name;
2341 static const char *normal_ext = "_normal.png";
2342 static const u32 normal_ext_size = strlen(normal_ext);
2343 size_t pos = fname_base.find(".");
2344 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2345 if (isKnownSourceImage(fname_normal)) {
2346 // look for image extension and replace it
2348 while ((i = fname_base.find(".", i)) != std::string::npos) {
2349 fname_base.replace(i, 4, normal_ext);
2350 i += normal_ext_size;
2352 return getTexture(fname_base);
2357 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2359 video::IVideoDriver *driver = m_device->getVideoDriver();
2360 video::SColor c(0, 0, 0, 0);
2361 video::ITexture *texture = getTexture(name);
2362 video::IImage *image = driver->createImage(texture,
2363 core::position2d<s32>(0, 0),
2364 texture->getOriginalSize());
2369 core::dimension2d<u32> dim = image->getDimension();
2372 step = dim.Width / 16;
2373 for (u16 x = 0; x < dim.Width; x += step) {
2374 for (u16 y = 0; y < dim.Width; y += step) {
2375 c = image->getPixel(x,y);
2376 if (c.getAlpha() > 0) {
2386 c.setRed(tR / total);
2387 c.setGreen(tG / total);
2388 c.setBlue(tB / total);
2395 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2397 std::string tname = "__shaderFlagsTexture";
2398 tname += normalmap_present ? "1" : "0";
2400 if (isKnownSourceImage(tname)) {
2401 return getTexture(tname);
2403 video::IVideoDriver *driver = m_device->getVideoDriver();
2404 video::IImage *flags_image = driver->createImage(
2405 video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2406 sanity_check(flags_image != NULL);
2407 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2408 flags_image->setPixel(0, 0, c);
2409 insertSourceImage(tname, flags_image);
2410 flags_image->drop();
2411 return getTexture(tname);