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 "main.h" // for g_settings
26 #include <ICameraSceneNode.h>
28 #include "mapnode.h" // For texture atlas making
29 #include "nodedef.h" // For texture atlas making
31 #include "util/string.h"
32 #include "util/container.h"
33 #include "util/thread.h"
34 #include "util/numeric.h"
37 A cache from texture name to texture path
39 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
42 Replaces the filename extension.
44 std::string image = "a/image.png"
45 replace_ext(image, "jpg")
46 -> image = "a/image.jpg"
47 Returns true on success.
49 static bool replace_ext(std::string &path, const char *ext)
53 // Find place of last dot, fail if \ or / found.
55 for(s32 i=path.size()-1; i>=0; i--)
63 if(path[i] == '\\' || path[i] == '/')
66 // If not found, return an empty string
69 // Else make the new path
70 path = path.substr(0, last_dot_i+1) + ext;
75 Find out the full path of an image by trying different filename
80 std::string getImagePath(std::string path)
82 // A NULL-ended list of possible image extensions
83 const char *extensions[] = {
84 "png", "jpg", "bmp", "tga",
85 "pcx", "ppm", "psd", "wal", "rgb",
88 // If there is no extension, add one
89 if(removeStringEnd(path, extensions) == "")
91 // Check paths until something is found to exist
92 const char **ext = extensions;
94 bool r = replace_ext(path, *ext);
97 if(fs::PathExists(path))
100 while((++ext) != NULL);
106 Gets the path to a texture by first checking if the texture exists
107 in texture_path and if not, using the data path.
109 Checks all supported extensions by replacing the original extension.
111 If not found, returns "".
113 Utilizes a thread-safe cache.
115 std::string getTexturePath(const std::string &filename)
117 std::string fullpath = "";
121 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
126 Check from texture_path
128 std::string texture_path = g_settings->get("texture_path");
129 if(texture_path != "")
131 std::string testpath = texture_path + DIR_DELIM + filename;
132 // Check all filename extensions. Returns "" if not found.
133 fullpath = getImagePath(testpath);
137 Check from $user/textures/all
141 std::string texture_path = porting::path_user + DIR_DELIM
142 + "textures" + DIR_DELIM + "all";
143 std::string testpath = texture_path + DIR_DELIM + filename;
144 // Check all filename extensions. Returns "" if not found.
145 fullpath = getImagePath(testpath);
149 Check from default data directory
153 std::string base_path = porting::path_share + DIR_DELIM + "textures"
154 + DIR_DELIM + "base" + DIR_DELIM + "pack";
155 std::string testpath = base_path + DIR_DELIM + filename;
156 // Check all filename extensions. Returns "" if not found.
157 fullpath = getImagePath(testpath);
160 // Add to cache (also an empty result is cached)
161 g_texturename_to_path_cache.set(filename, fullpath);
168 An internal variant of AtlasPointer with more data.
169 (well, more like a wrapper)
172 struct SourceAtlasPointer
176 video::IImage *atlas_img; // The source image of the atlas
177 // Integer variants of position and size
182 const std::string &name_,
183 AtlasPointer a_=AtlasPointer(0, NULL),
184 video::IImage *atlas_img_=NULL,
185 v2s32 intpos_=v2s32(0,0),
186 v2u32 intsize_=v2u32(0,0)
190 atlas_img(atlas_img_),
198 SourceImageCache: A cache used for storing source images.
201 class SourceImageCache
204 ~SourceImageCache() {
205 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
206 iter != m_images.end(); iter++) {
207 iter->second->drop();
211 void insert(const std::string &name, video::IImage *img,
212 bool prefer_local, video::IVideoDriver *driver)
216 std::map<std::string, video::IImage*>::iterator n;
217 n = m_images.find(name);
218 if(n != m_images.end()){
223 video::IImage* toadd = img;
224 bool need_to_grab = true;
226 // Try to use local texture instead if asked to
228 std::string path = getTexturePath(name.c_str());
230 video::IImage *img2 = driver->createImageFromFile(path.c_str());
233 need_to_grab = false;
240 m_images[name] = toadd;
242 video::IImage* get(const std::string &name)
244 std::map<std::string, video::IImage*>::iterator n;
245 n = m_images.find(name);
246 if(n != m_images.end())
250 // Primarily fetches from cache, secondarily tries to read from filesystem
251 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
253 std::map<std::string, video::IImage*>::iterator n;
254 n = m_images.find(name);
255 if(n != m_images.end()){
256 n->second->grab(); // Grab for caller
259 video::IVideoDriver* driver = device->getVideoDriver();
260 std::string path = getTexturePath(name.c_str());
262 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
263 <<name<<"\""<<std::endl;
266 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
268 video::IImage *img = driver->createImageFromFile(path.c_str());
271 m_images[name] = img;
272 img->grab(); // Grab for caller
277 std::map<std::string, video::IImage*> m_images;
284 class TextureSource : public IWritableTextureSource
287 TextureSource(IrrlichtDevice *device);
288 virtual ~TextureSource();
292 Now, assume a texture with the id 1 exists, and has the name
293 "stone.png^mineral1".
294 Then a random thread calls getTextureId for a texture called
295 "stone.png^mineral1^crack0".
296 ...Now, WTF should happen? Well:
297 - getTextureId strips off stuff recursively from the end until
298 the remaining part is found, or nothing is left when
299 something is stripped out
301 But it is slow to search for textures by names and modify them
303 - ContentFeatures is made to contain ids for the basic plain
305 - Crack textures can be slow by themselves, but the framework
309 - Assume a texture with the id 1 exists, and has the name
310 "stone.png^mineral1" and is specified as a part of some atlas.
311 - Now getNodeTile() stumbles upon a node which uses
312 texture id 1, and determines that MATERIAL_FLAG_CRACK
313 must be applied to the tile
314 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
315 has received the current crack level 0 from the client. It
316 finds out the name of the texture with getTextureName(1),
317 appends "^crack0" to it and gets a new texture id with
318 getTextureId("stone.png^mineral1^crack0").
323 Gets a texture id from cache or
324 - if main thread, from getTextureIdDirect
325 - if other thread, adds to request queue and waits for main thread
327 u32 getTextureId(const std::string &name);
333 "stone.png^mineral_coal.png"
334 "stone.png^mineral_coal.png^crack1"
336 - If texture specified by name is found from cache, return the
338 - Otherwise generate the texture, add to cache and return id.
339 Recursion is used to find out the largest found part of the
340 texture and continue based on it.
342 The id 0 points to a NULL texture. It is returned in case of error.
344 u32 getTextureIdDirect(const std::string &name);
346 // Finds out the name of a cached texture.
347 std::string getTextureName(u32 id);
350 If texture specified by the name pointed by the id doesn't
351 exist, create it, then return the cached texture.
353 Can be called from any thread. If called from some other thread
354 and not found in cache, the call is queued to the main thread
357 AtlasPointer getTexture(u32 id);
359 AtlasPointer getTexture(const std::string &name)
361 return getTexture(getTextureId(name));
364 // Gets a separate texture
365 video::ITexture* getTextureRaw(const std::string &name)
367 AtlasPointer ap = getTexture(name + "^[forcesingle");
371 // Gets a separate texture atlas pointer
372 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
374 return getTexture(getTextureName(ap.id) + "^[forcesingle");
377 // Returns a pointer to the irrlicht device
378 virtual IrrlichtDevice* getDevice()
383 // Update new texture pointer and texture coordinates to an
384 // AtlasPointer based on it's texture id
385 void updateAP(AtlasPointer &ap);
387 bool isKnownSourceImage(const std::string &name)
389 bool is_known = false;
390 bool cache_found = m_source_image_existence.get(name, &is_known);
393 // Not found in cache; find out if a local file exists
394 is_known = (getTexturePath(name) != "");
395 m_source_image_existence.set(name, is_known);
399 // Processes queued texture requests from other threads.
400 // Shall be called from the main thread.
403 // Insert an image into the cache without touching the filesystem.
404 // Shall be called from the main thread.
405 void insertSourceImage(const std::string &name, video::IImage *img);
407 // Rebuild images and textures from the current set of source images
408 // Shall be called from the main thread.
409 void rebuildImagesAndTextures();
411 // Build the main texture atlas which contains most of the
413 void buildMainAtlas(class IGameDef *gamedef);
417 // The id of the thread that is allowed to use irrlicht directly
418 threadid_t m_main_thread;
419 // The irrlicht device
420 IrrlichtDevice *m_device;
422 // Cache of source images
423 // This should be only accessed from the main thread
424 SourceImageCache m_sourcecache;
426 // Thread-safe cache of what source images are known (true = known)
427 MutexedMap<std::string, bool> m_source_image_existence;
429 // A texture id is index in this array.
430 // The first position contains a NULL texture.
431 std::vector<SourceAtlasPointer> m_atlaspointer_cache;
432 // Maps a texture name to an index in the former.
433 std::map<std::string, u32> m_name_to_id;
434 // The two former containers are behind this mutex
435 JMutex m_atlaspointer_cache_mutex;
437 // Main texture atlas. This is filled at startup and is then not touched.
438 video::IImage *m_main_atlas_image;
439 video::ITexture *m_main_atlas_texture;
441 // Queued texture fetches (to be processed by the main thread)
442 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
444 // Textures that have been overwritten with other ones
445 // but can't be deleted because the ITexture* might still be used
446 std::list<video::ITexture*> m_texture_trash;
449 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
451 return new TextureSource(device);
454 TextureSource::TextureSource(IrrlichtDevice *device):
456 m_main_atlas_image(NULL),
457 m_main_atlas_texture(NULL)
461 m_atlaspointer_cache_mutex.Init();
463 m_main_thread = get_current_thread_id();
465 // Add a NULL AtlasPointer as the first index, named ""
466 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
467 m_name_to_id[""] = 0;
470 TextureSource::~TextureSource()
472 video::IVideoDriver* driver = m_device->getVideoDriver();
474 unsigned int textures_before = driver->getTextureCount();
476 for (std::vector<SourceAtlasPointer>::iterator iter =
477 m_atlaspointer_cache.begin(); iter != m_atlaspointer_cache.end();
480 video::ITexture *t = driver->getTexture(iter->name.c_str());
484 driver->removeTexture(t);
486 //cleanup source image
488 iter->atlas_img->drop();
490 m_atlaspointer_cache.clear();
492 for (std::list<video::ITexture*>::iterator iter =
493 m_texture_trash.begin(); iter != m_texture_trash.end();
496 video::ITexture *t = *iter;
498 //cleanup trashed texture
499 driver->removeTexture(t);
502 infostream << "~TextureSource() "<< textures_before << "/"
503 << driver->getTextureCount() << std::endl;
506 u32 TextureSource::getTextureId(const std::string &name)
508 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
512 See if texture already exists
514 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
515 std::map<std::string, u32>::iterator n;
516 n = m_name_to_id.find(name);
517 if(n != m_name_to_id.end())
526 if(get_current_thread_id() == m_main_thread)
528 return getTextureIdDirect(name);
532 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
534 // We're gonna ask the result to be put into here
535 ResultQueue<std::string, u32, u8, u8> result_queue;
537 // Throw a request in
538 m_get_texture_queue.add(name, 0, 0, &result_queue);
540 infostream<<"Waiting for texture from main thread, name=\""
541 <<name<<"\""<<std::endl;
545 // Wait result for a second
546 GetResult<std::string, u32, u8, u8>
547 result = result_queue.pop_front(1000);
549 // Check that at least something worked OK
550 assert(result.key == name);
554 catch(ItemNotFoundException &e)
556 infostream<<"Waiting for texture timed out."<<std::endl;
561 infostream<<"getTextureId(): Failed"<<std::endl;
566 // Overlay image on top of another image (used for cracks)
567 void overlay(video::IImage *image, video::IImage *overlay);
569 // Draw an image on top of an another one, using the alpha channel of the
571 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
572 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
575 void brighten(video::IImage *image);
576 // Parse a transform name
577 u32 parseImageTransform(const std::string& s);
578 // Apply transform to image dimension
579 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
580 // Apply transform to image data
581 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
584 Generate image based on a string like "stone.png" or "[crack0".
585 if baseimg is NULL, it is created. Otherwise stuff is made on it.
587 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
588 IrrlichtDevice *device, SourceImageCache *sourcecache);
591 Generates an image from a full string like
592 "stone.png^mineral_coal.png^[crack0".
594 This is used by buildMainAtlas().
596 video::IImage* generate_image_from_scratch(std::string name,
597 IrrlichtDevice *device, SourceImageCache *sourcecache);
600 This method generates all the textures
602 u32 TextureSource::getTextureIdDirect(const std::string &name)
604 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
606 // Empty name means texture 0
609 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
614 Calling only allowed from main thread
616 if(get_current_thread_id() != m_main_thread)
618 errorstream<<"TextureSource::getTextureIdDirect() "
619 "called not from main thread"<<std::endl;
624 See if texture already exists
627 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
629 std::map<std::string, u32>::iterator n;
630 n = m_name_to_id.find(name);
631 if(n != m_name_to_id.end())
633 /*infostream<<"getTextureIdDirect(): \""<<name
634 <<"\" found in cache"<<std::endl;*/
639 /*infostream<<"getTextureIdDirect(): \""<<name
640 <<"\" NOT found in cache. Creating it."<<std::endl;*/
646 char separator = '^';
649 This is set to the id of the base image.
650 If left 0, there is no base image and a completely new image
653 u32 base_image_id = 0;
655 // Find last meta separator in name
656 s32 last_separator_position = -1;
657 for(s32 i=name.size()-1; i>=0; i--)
659 if(name[i] == separator)
661 last_separator_position = i;
666 If separator was found, construct the base name and make the
667 base image using a recursive call
669 std::string base_image_name;
670 if(last_separator_position != -1)
672 // Construct base name
673 base_image_name = name.substr(0, last_separator_position);
674 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
675 " to get base image of \""<<name<<"\" = \""
676 <<base_image_name<<"\""<<std::endl;*/
677 base_image_id = getTextureIdDirect(base_image_name);
680 //infostream<<"base_image_id="<<base_image_id<<std::endl;
682 video::IVideoDriver* driver = m_device->getVideoDriver();
685 video::ITexture *t = NULL;
688 An image will be built from files and then converted into a texture.
690 video::IImage *baseimg = NULL;
692 // If a base image was found, copy it to baseimg
693 if(base_image_id != 0)
695 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
697 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
699 video::IImage *image = ap.atlas_img;
703 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
704 <<"cache: \""<<base_image_name<<"\""
709 core::dimension2d<u32> dim = ap.intsize;
711 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
713 core::position2d<s32> pos_to(0,0);
714 core::position2d<s32> pos_from = ap.intpos;
718 v2s32(0,0), // position in target
719 core::rect<s32>(pos_from, dim) // from
722 /*infostream<<"getTextureIdDirect(): Loaded \""
723 <<base_image_name<<"\" from image cache"
729 Parse out the last part of the name of the image and act
733 std::string last_part_of_name = name.substr(last_separator_position+1);
734 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
736 // Generate image according to part of name
737 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
739 errorstream<<"getTextureIdDirect(): "
740 "failed to generate \""<<last_part_of_name<<"\""
744 // If no resulting image, print a warning
747 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
748 " create texture \""<<name<<"\""<<std::endl;
753 // Create texture from resulting image
754 t = driver->addTexture(name.c_str(), baseimg);
758 Add texture to caches (add NULL textures too)
761 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
763 u32 id = m_atlaspointer_cache.size();
769 core::dimension2d<u32> baseimg_dim(0,0);
771 baseimg_dim = baseimg->getDimension();
772 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
773 m_atlaspointer_cache.push_back(nap);
774 m_name_to_id[name] = id;
776 /*infostream<<"getTextureIdDirect(): "
777 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
782 std::string TextureSource::getTextureName(u32 id)
784 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
786 if(id >= m_atlaspointer_cache.size())
788 errorstream<<"TextureSource::getTextureName(): id="<<id
789 <<" >= m_atlaspointer_cache.size()="
790 <<m_atlaspointer_cache.size()<<std::endl;
794 return m_atlaspointer_cache[id].name;
798 AtlasPointer TextureSource::getTexture(u32 id)
800 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
802 if(id >= m_atlaspointer_cache.size())
803 return AtlasPointer(0, NULL);
805 return m_atlaspointer_cache[id].a;
808 void TextureSource::updateAP(AtlasPointer &ap)
810 AtlasPointer ap2 = getTexture(ap.id);
814 void TextureSource::processQueue()
819 if(!m_get_texture_queue.empty())
821 GetRequest<std::string, u32, u8, u8>
822 request = m_get_texture_queue.pop();
824 /*infostream<<"TextureSource::processQueue(): "
825 <<"got texture request with "
826 <<"name=\""<<request.key<<"\""
829 GetResult<std::string, u32, u8, u8>
831 result.key = request.key;
832 result.callers = request.callers;
833 result.item = getTextureIdDirect(request.key);
835 request.dest->push_back(result);
839 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
841 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
843 assert(get_current_thread_id() == m_main_thread);
845 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
846 m_source_image_existence.set(name, true);
849 void TextureSource::rebuildImagesAndTextures()
851 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
853 /*// Oh well... just clear everything, they'll load sometime.
854 m_atlaspointer_cache.clear();
855 m_name_to_id.clear();*/
857 video::IVideoDriver* driver = m_device->getVideoDriver();
859 // Remove source images from textures to disable inheriting textures
860 // from existing textures
861 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
862 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
863 sap->atlas_img->drop();
864 sap->atlas_img = NULL;
868 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
869 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
871 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
872 // Create texture from resulting image
873 video::ITexture *t = NULL;
875 t = driver->addTexture(sap->name.c_str(), img);
876 video::ITexture *t_old = sap->a.atlas;
879 sap->a.pos = v2f(0,0);
880 sap->a.size = v2f(1,1);
882 sap->atlas_img = img;
883 sap->intpos = v2s32(0,0);
884 sap->intsize = img->getDimension();
887 m_texture_trash.push_back(t_old);
891 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
893 assert(gamedef->tsrc() == this);
894 INodeDefManager *ndef = gamedef->ndef();
896 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
898 //return; // Disable (for testing)
900 video::IVideoDriver* driver = m_device->getVideoDriver();
903 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
905 // Create an image of the right size
906 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
907 core::dimension2d<u32> atlas_dim(2048,2048);
908 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
909 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
910 video::IImage *atlas_img =
911 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
913 if(atlas_img == NULL)
915 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
916 "image; not building texture atlas."<<std::endl;
921 Grab list of stuff to include in the texture atlas from the
922 main content features
925 std::set<std::string> sourcelist;
927 for(u16 j=0; j<MAX_CONTENT+1; j++)
929 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
931 const ContentFeatures &f = ndef->get(j);
932 for(u32 i=0; i<6; i++)
934 std::string name = f.tiledef[i].name;
935 sourcelist.insert(name);
939 infostream<<"Creating texture atlas out of textures: ";
940 for(std::set<std::string>::iterator
941 i = sourcelist.begin();
942 i != sourcelist.end(); ++i)
944 std::string name = *i;
945 infostream<<"\""<<name<<"\" ";
947 infostream<<std::endl;
949 // Padding to disallow texture bleeding
950 // (16 needed if mipmapping is used; otherwise less will work too)
952 s32 column_padding = 16;
953 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
956 First pass: generate almost everything
958 core::position2d<s32> pos_in_atlas(0,0);
960 pos_in_atlas.X = column_padding;
961 pos_in_atlas.Y = padding;
963 for(std::set<std::string>::iterator
964 i = sourcelist.begin();
965 i != sourcelist.end(); ++i)
967 std::string name = *i;
969 // Generate image by name
970 video::IImage *img2 = generate_image_from_scratch(name, m_device,
974 errorstream<<"TextureSource::buildMainAtlas(): "
975 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
979 core::dimension2d<u32> dim = img2->getDimension();
981 // Don't add to atlas if image is too large
982 core::dimension2d<u32> max_size_in_atlas(64,64);
983 if(dim.Width > max_size_in_atlas.Width
984 || dim.Height > max_size_in_atlas.Height)
986 infostream<<"TextureSource::buildMainAtlas(): Not adding "
987 <<"\""<<name<<"\" because image is large"<<std::endl;
991 // Wrap columns and stop making atlas if atlas is full
992 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
994 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
995 errorstream<<"TextureSource::buildMainAtlas(): "
996 <<"Atlas is full, not adding more textures."
1000 pos_in_atlas.Y = padding;
1001 pos_in_atlas.X += column_width + column_padding*2;
1004 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
1005 <<"\" to texture atlas"<<std::endl;*/
1007 // Tile it a few times in the X direction
1008 u16 xwise_tiling = column_width / dim.Width;
1009 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
1011 for(u32 j=0; j<xwise_tiling; j++)
1013 // Copy the copy to the atlas
1014 /*img2->copyToWithAlpha(atlas_img,
1015 pos_in_atlas + v2s32(j*dim.Width,0),
1016 core::rect<s32>(v2s32(0,0), dim),
1017 video::SColor(255,255,255,255),
1019 img2->copyTo(atlas_img,
1020 pos_in_atlas + v2s32(j*dim.Width,0),
1021 core::rect<s32>(v2s32(0,0), dim),
1025 // Copy the borders a few times to disallow texture bleeding
1026 for(u32 side=0; side<2; side++) // top and bottom
1027 for(s32 y0=0; y0<padding; y0++)
1028 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
1034 dst_y = y0 + pos_in_atlas.Y + dim.Height;
1035 src_y = pos_in_atlas.Y + dim.Height - 1;
1039 dst_y = -y0 + pos_in_atlas.Y-1;
1040 src_y = pos_in_atlas.Y;
1042 s32 x = x0 + pos_in_atlas.X;
1043 video::SColor c = atlas_img->getPixel(x, src_y);
1044 atlas_img->setPixel(x,dst_y,c);
1047 for(u32 side=0; side<2; side++) // left and right
1048 for(s32 x0=0; x0<column_padding; x0++)
1049 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
1055 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
1056 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1060 dst_x = -x0 + pos_in_atlas.X-1;
1061 src_x = pos_in_atlas.X;
1063 s32 y = y0 + pos_in_atlas.Y;
1064 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1066 video::SColor c = atlas_img->getPixel(src_x, src_y);
1067 atlas_img->setPixel(dst_x,dst_y,c);
1073 Add texture to caches
1076 bool reuse_old_id = false;
1077 u32 id = m_atlaspointer_cache.size();
1078 // Check old id without fetching a texture
1079 std::map<std::string, u32>::iterator n;
1080 n = m_name_to_id.find(name);
1081 // If it exists, we will replace the old definition
1082 if(n != m_name_to_id.end()){
1084 reuse_old_id = true;
1085 /*infostream<<"TextureSource::buildMainAtlas(): "
1086 <<"Replacing old AtlasPointer"<<std::endl;*/
1089 // Create AtlasPointer
1090 AtlasPointer ap(id);
1091 ap.atlas = NULL; // Set on the second pass
1092 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1093 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1094 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1095 (float)dim.Width/(float)atlas_dim.Height);
1096 ap.tiled = xwise_tiling;
1098 // Create SourceAtlasPointer and add to containers
1099 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1101 m_atlaspointer_cache[id] = nap;
1103 m_atlaspointer_cache.push_back(nap);
1104 m_name_to_id[name] = id;
1106 // Increment position
1107 pos_in_atlas.Y += dim.Height + padding * 2;
1113 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1117 Second pass: set texture pointer in generated AtlasPointers
1119 for(std::set<std::string>::iterator
1120 i = sourcelist.begin();
1121 i != sourcelist.end(); ++i)
1123 std::string name = *i;
1124 if(m_name_to_id.find(name) == m_name_to_id.end())
1126 u32 id = m_name_to_id[name];
1127 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1128 m_atlaspointer_cache[id].a.atlas = t;
1132 Write image to file so that it can be inspected
1134 /*std::string atlaspath = porting::path_user
1135 + DIR_DELIM + "generated_texture_atlas.png";
1136 infostream<<"Removing and writing texture atlas for inspection to "
1137 <<atlaspath<<std::endl;
1138 fs::RecursiveDelete(atlaspath);
1139 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1142 video::IImage* generate_image_from_scratch(std::string name,
1143 IrrlichtDevice *device, SourceImageCache *sourcecache)
1145 /*infostream<<"generate_image_from_scratch(): "
1146 "\""<<name<<"\""<<std::endl;*/
1148 video::IVideoDriver* driver = device->getVideoDriver();
1155 video::IImage *baseimg = NULL;
1157 char separator = '^';
1159 // Find last meta separator in name
1160 s32 last_separator_position = name.find_last_of(separator);
1161 //if(last_separator_position == std::npos)
1162 // last_separator_position = -1;
1164 /*infostream<<"generate_image_from_scratch(): "
1165 <<"last_separator_position="<<last_separator_position
1169 If separator was found, construct the base name and make the
1170 base image using a recursive call
1172 std::string base_image_name;
1173 if(last_separator_position != -1)
1175 // Construct base name
1176 base_image_name = name.substr(0, last_separator_position);
1177 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1178 " to get base image of \""<<name<<"\" = \""
1179 <<base_image_name<<"\""<<std::endl;*/
1180 baseimg = generate_image_from_scratch(base_image_name, device,
1185 Parse out the last part of the name of the image and act
1189 std::string last_part_of_name = name.substr(last_separator_position+1);
1190 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1192 // Generate image according to part of name
1193 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1195 errorstream<<"generate_image_from_scratch(): "
1196 "failed to generate \""<<last_part_of_name<<"\""
1204 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1205 IrrlichtDevice *device, SourceImageCache *sourcecache)
1207 video::IVideoDriver* driver = device->getVideoDriver();
1210 // Stuff starting with [ are special commands
1211 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1213 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1217 if(part_of_name != ""){
1218 errorstream<<"generate_image(): Could not load image \""
1219 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1220 errorstream<<"generate_image(): Creating a dummy"
1221 <<" image 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);
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.
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),
1272 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1279 // A special texture modification
1281 /*infostream<<"generate_image(): generating special "
1282 <<"modification \""<<part_of_name<<"\""
1286 This is the simplest of all; it just adds stuff to the
1287 name so that a separate texture is created.
1289 It is used to make textures for stuff that doesn't want
1290 to implement getting the texture from a bigger texture
1293 if(part_of_name == "[forcesingle")
1295 // If base image is NULL, create a random color
1298 core::dimension2d<u32> dim(1,1);
1299 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1301 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1302 myrand()%256,myrand()%256));
1307 Adds a cracking texture
1309 else if(part_of_name.substr(0,6) == "[crack")
1313 errorstream<<"generate_image(): baseimg==NULL "
1314 <<"for part_of_name=\""<<part_of_name
1315 <<"\", cancelling."<<std::endl;
1319 // Crack image number and overlay option
1320 s32 progression = 0;
1321 bool use_overlay = false;
1322 if(part_of_name.substr(6,1) == "o")
1324 progression = stoi(part_of_name.substr(7));
1329 progression = stoi(part_of_name.substr(6));
1330 use_overlay = false;
1333 // Size of the base image
1334 core::dimension2d<u32> dim_base = baseimg->getDimension();
1339 It is an image with a number of cracking stages
1342 video::IImage *img_crack = sourcecache->getOrLoad(
1343 "crack_anylength.png", device);
1345 if(img_crack && progression >= 0)
1347 // Dimension of original image
1348 core::dimension2d<u32> dim_crack
1349 = img_crack->getDimension();
1350 // Count of crack stages
1351 s32 crack_count = dim_crack.Height / dim_crack.Width;
1352 // Limit progression
1353 if(progression > crack_count-1)
1354 progression = crack_count-1;
1355 // Dimension of a single crack stage
1356 core::dimension2d<u32> dim_crack_cropped(
1360 // Create cropped and scaled crack images
1361 video::IImage *img_crack_cropped = driver->createImage(
1362 video::ECF_A8R8G8B8, dim_crack_cropped);
1363 video::IImage *img_crack_scaled = driver->createImage(
1364 video::ECF_A8R8G8B8, dim_base);
1366 if(img_crack_cropped && img_crack_scaled)
1369 v2s32 pos_crack(0, progression*dim_crack.Width);
1370 img_crack->copyTo(img_crack_cropped,
1372 core::rect<s32>(pos_crack, dim_crack_cropped));
1373 // Scale crack image by copying
1374 img_crack_cropped->copyToScaling(img_crack_scaled);
1375 // Copy or overlay crack image
1378 overlay(baseimg, img_crack_scaled);
1382 /*img_crack_scaled->copyToWithAlpha(
1385 core::rect<s32>(v2s32(0,0), dim_base),
1386 video::SColor(255,255,255,255));*/
1387 blit_with_alpha(img_crack_scaled, baseimg,
1388 v2s32(0,0), v2s32(0,0), dim_base);
1392 if(img_crack_scaled)
1393 img_crack_scaled->drop();
1395 if(img_crack_cropped)
1396 img_crack_cropped->drop();
1402 [combine:WxH:X,Y=filename:X,Y=filename2
1403 Creates a bigger texture from an amount of smaller ones
1405 else if(part_of_name.substr(0,8) == "[combine")
1407 Strfnd sf(part_of_name);
1409 u32 w0 = stoi(sf.next("x"));
1410 u32 h0 = stoi(sf.next(":"));
1411 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1412 core::dimension2d<u32> dim(w0,h0);
1415 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1416 baseimg->fill(video::SColor(0,0,0,0));
1418 while(sf.atend() == false)
1420 u32 x = stoi(sf.next(","));
1421 u32 y = stoi(sf.next("="));
1422 std::string filename = sf.next(":");
1423 infostream<<"Adding \""<<filename
1424 <<"\" to combined ("<<x<<","<<y<<")"
1426 video::IImage *img = sourcecache->getOrLoad(filename, device);
1429 core::dimension2d<u32> dim = img->getDimension();
1430 infostream<<"Size "<<dim.Width
1431 <<"x"<<dim.Height<<std::endl;
1432 core::position2d<s32> pos_base(x, y);
1433 video::IImage *img2 =
1434 driver->createImage(video::ECF_A8R8G8B8, dim);
1437 /*img2->copyToWithAlpha(baseimg, pos_base,
1438 core::rect<s32>(v2s32(0,0), dim),
1439 video::SColor(255,255,255,255),
1441 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1446 infostream<<"img==NULL"<<std::endl;
1453 else if(part_of_name.substr(0,9) == "[brighten")
1457 errorstream<<"generate_image(): baseimg==NULL "
1458 <<"for part_of_name=\""<<part_of_name
1459 <<"\", cancelling."<<std::endl;
1467 Make image completely opaque.
1468 Used for the leaves texture when in old leaves mode, so
1469 that the transparent parts don't look completely black
1470 when simple alpha channel is used for rendering.
1472 else if(part_of_name.substr(0,8) == "[noalpha")
1476 errorstream<<"generate_image(): baseimg==NULL "
1477 <<"for part_of_name=\""<<part_of_name
1478 <<"\", cancelling."<<std::endl;
1482 core::dimension2d<u32> dim = baseimg->getDimension();
1484 // Set alpha to full
1485 for(u32 y=0; y<dim.Height; y++)
1486 for(u32 x=0; x<dim.Width; x++)
1488 video::SColor c = baseimg->getPixel(x,y);
1490 baseimg->setPixel(x,y,c);
1495 Convert one color to transparent.
1497 else if(part_of_name.substr(0,11) == "[makealpha:")
1501 errorstream<<"generate_image(): baseimg==NULL "
1502 <<"for part_of_name=\""<<part_of_name
1503 <<"\", cancelling."<<std::endl;
1507 Strfnd sf(part_of_name.substr(11));
1508 u32 r1 = stoi(sf.next(","));
1509 u32 g1 = stoi(sf.next(","));
1510 u32 b1 = stoi(sf.next(""));
1511 std::string filename = sf.next("");
1513 core::dimension2d<u32> dim = baseimg->getDimension();
1515 /*video::IImage *oldbaseimg = baseimg;
1516 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1517 oldbaseimg->copyTo(baseimg);
1518 oldbaseimg->drop();*/
1520 // Set alpha to full
1521 for(u32 y=0; y<dim.Height; y++)
1522 for(u32 x=0; x<dim.Width; x++)
1524 video::SColor c = baseimg->getPixel(x,y);
1526 u32 g = c.getGreen();
1527 u32 b = c.getBlue();
1528 if(!(r == r1 && g == g1 && b == b1))
1531 baseimg->setPixel(x,y,c);
1536 Rotates and/or flips the image.
1538 N can be a number (between 0 and 7) or a transform name.
1539 Rotations are counter-clockwise.
1541 1 R90 rotate by 90 degrees
1542 2 R180 rotate by 180 degrees
1543 3 R270 rotate by 270 degrees
1545 5 FXR90 flip X then rotate by 90 degrees
1547 7 FYR90 flip Y then rotate by 90 degrees
1549 Note: Transform names can be concatenated to produce
1550 their product (applies the first then the second).
1551 The resulting transform will be equivalent to one of the
1552 eight existing ones, though (see: dihedral group).
1554 else if(part_of_name.substr(0,10) == "[transform")
1558 errorstream<<"generate_image(): baseimg==NULL "
1559 <<"for part_of_name=\""<<part_of_name
1560 <<"\", cancelling."<<std::endl;
1564 u32 transform = parseImageTransform(part_of_name.substr(10));
1565 core::dimension2d<u32> dim = imageTransformDimension(
1566 transform, baseimg->getDimension());
1567 video::IImage *image = driver->createImage(
1568 baseimg->getColorFormat(), dim);
1570 imageTransform(transform, baseimg, image);
1575 [inventorycube{topimage{leftimage{rightimage
1576 In every subimage, replace ^ with &.
1577 Create an "inventory cube".
1578 NOTE: This should be used only on its own.
1579 Example (a grass block (not actually used in game):
1580 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1582 else if(part_of_name.substr(0,14) == "[inventorycube")
1586 errorstream<<"generate_image(): baseimg!=NULL "
1587 <<"for part_of_name=\""<<part_of_name
1588 <<"\", cancelling."<<std::endl;
1592 str_replace_char(part_of_name, '&', '^');
1593 Strfnd sf(part_of_name);
1595 std::string imagename_top = sf.next("{");
1596 std::string imagename_left = sf.next("{");
1597 std::string imagename_right = sf.next("{");
1599 // Generate images for the faces of the cube
1600 video::IImage *img_top = generate_image_from_scratch(
1601 imagename_top, device, sourcecache);
1602 video::IImage *img_left = generate_image_from_scratch(
1603 imagename_left, device, sourcecache);
1604 video::IImage *img_right = generate_image_from_scratch(
1605 imagename_right, device, sourcecache);
1606 assert(img_top && img_left && img_right);
1608 // Create textures from images
1609 video::ITexture *texture_top = driver->addTexture(
1610 (imagename_top + "__temp__").c_str(), img_top);
1611 video::ITexture *texture_left = driver->addTexture(
1612 (imagename_left + "__temp__").c_str(), img_left);
1613 video::ITexture *texture_right = driver->addTexture(
1614 (imagename_right + "__temp__").c_str(), img_right);
1615 assert(texture_top && texture_left && texture_right);
1623 Draw a cube mesh into a render target texture
1625 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1626 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1627 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1628 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1629 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1630 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1631 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1632 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1634 core::dimension2d<u32> dim(64,64);
1635 std::string rtt_texture_name = part_of_name + "_RTT";
1637 v3f camera_position(0, 1.0, -1.5);
1638 camera_position.rotateXZBy(45);
1639 v3f camera_lookat(0, 0, 0);
1640 core::CMatrix4<f32> camera_projection_matrix;
1641 // Set orthogonal projection
1642 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1643 1.65, 1.65, 0, 100);
1645 video::SColorf ambient_light(0.2,0.2,0.2);
1646 v3f light_position(10, 100, -50);
1647 video::SColorf light_color(0.5,0.5,0.5);
1648 f32 light_radius = 1000;
1650 video::ITexture *rtt = generateTextureFromMesh(
1651 cube, device, dim, rtt_texture_name,
1654 camera_projection_matrix,
1663 // Free textures of images
1664 driver->removeTexture(texture_top);
1665 driver->removeTexture(texture_left);
1666 driver->removeTexture(texture_right);
1670 baseimg = generate_image_from_scratch(
1671 imagename_top, device, sourcecache);
1675 // Create image of render target
1676 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1680 driver->removeTexture(rtt);
1682 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1686 image->copyTo(baseimg);
1691 [lowpart:percent:filename
1692 Adds the lower part of a texture
1694 else if(part_of_name.substr(0,9) == "[lowpart:")
1696 Strfnd sf(part_of_name);
1698 u32 percent = stoi(sf.next(":"));
1699 std::string filename = sf.next(":");
1700 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1703 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1704 video::IImage *img = sourcecache->getOrLoad(filename, device);
1707 core::dimension2d<u32> dim = img->getDimension();
1708 core::position2d<s32> pos_base(0, 0);
1709 video::IImage *img2 =
1710 driver->createImage(video::ECF_A8R8G8B8, dim);
1713 core::position2d<s32> clippos(0, 0);
1714 clippos.Y = dim.Height * (100-percent) / 100;
1715 core::dimension2d<u32> clipdim = dim;
1716 clipdim.Height = clipdim.Height * percent / 100 + 1;
1717 core::rect<s32> cliprect(clippos, clipdim);
1718 img2->copyToWithAlpha(baseimg, pos_base,
1719 core::rect<s32>(v2s32(0,0), dim),
1720 video::SColor(255,255,255,255),
1727 Crops a frame of a vertical animation.
1728 N = frame count, I = frame index
1730 else if(part_of_name.substr(0,15) == "[verticalframe:")
1732 Strfnd sf(part_of_name);
1734 u32 frame_count = stoi(sf.next(":"));
1735 u32 frame_index = stoi(sf.next(":"));
1737 if(baseimg == NULL){
1738 errorstream<<"generate_image(): baseimg!=NULL "
1739 <<"for part_of_name=\""<<part_of_name
1740 <<"\", cancelling."<<std::endl;
1744 v2u32 frame_size = baseimg->getDimension();
1745 frame_size.Y /= frame_count;
1747 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1750 errorstream<<"generate_image(): Could not create image "
1751 <<"for part_of_name=\""<<part_of_name
1752 <<"\", cancelling."<<std::endl;
1756 // Fill target image with transparency
1757 img->fill(video::SColor(0,0,0,0));
1759 core::dimension2d<u32> dim = frame_size;
1760 core::position2d<s32> pos_dst(0, 0);
1761 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1762 baseimg->copyToWithAlpha(img, pos_dst,
1763 core::rect<s32>(pos_src, dim),
1764 video::SColor(255,255,255,255),
1772 errorstream<<"generate_image(): Invalid "
1773 " modification: \""<<part_of_name<<"\""<<std::endl;
1780 void overlay(video::IImage *image, video::IImage *overlay)
1783 Copy overlay to image, taking alpha into account.
1784 Where image is transparent, don't copy from overlay.
1785 Images sizes must be identical.
1787 if(image == NULL || overlay == NULL)
1790 core::dimension2d<u32> dim = image->getDimension();
1791 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1792 assert(dim == dim_overlay);
1794 for(u32 y=0; y<dim.Height; y++)
1795 for(u32 x=0; x<dim.Width; x++)
1797 video::SColor c1 = image->getPixel(x,y);
1798 video::SColor c2 = overlay->getPixel(x,y);
1799 u32 a1 = c1.getAlpha();
1800 u32 a2 = c2.getAlpha();
1801 if(a1 == 255 && a2 != 0)
1803 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1804 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1805 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1807 image->setPixel(x,y,c1);
1812 Draw an image on top of an another one, using the alpha channel of the
1815 This exists because IImage::copyToWithAlpha() doesn't seem to always
1818 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1819 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1821 for(u32 y0=0; y0<size.Y; y0++)
1822 for(u32 x0=0; x0<size.X; x0++)
1824 s32 src_x = src_pos.X + x0;
1825 s32 src_y = src_pos.Y + y0;
1826 s32 dst_x = dst_pos.X + x0;
1827 s32 dst_y = dst_pos.Y + y0;
1828 video::SColor src_c = src->getPixel(src_x, src_y);
1829 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1830 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1831 dst->setPixel(dst_x, dst_y, dst_c);
1835 void brighten(video::IImage *image)
1840 core::dimension2d<u32> dim = image->getDimension();
1842 for(u32 y=0; y<dim.Height; y++)
1843 for(u32 x=0; x<dim.Width; x++)
1845 video::SColor c = image->getPixel(x,y);
1846 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1847 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1848 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1849 image->setPixel(x,y,c);
1853 u32 parseImageTransform(const std::string& s)
1855 int total_transform = 0;
1857 std::string transform_names[8];
1858 transform_names[0] = "i";
1859 transform_names[1] = "r90";
1860 transform_names[2] = "r180";
1861 transform_names[3] = "r270";
1862 transform_names[4] = "fx";
1863 transform_names[6] = "fy";
1865 std::size_t pos = 0;
1866 while(pos < s.size())
1869 for(int i = 0; i <= 7; ++i)
1871 const std::string &name_i = transform_names[i];
1873 if(s[pos] == ('0' + i))
1879 else if(!(name_i.empty()) &&
1880 lowercase(s.substr(pos, name_i.size())) == name_i)
1883 pos += name_i.size();
1890 // Multiply total_transform and transform in the group D4
1893 new_total = (transform + total_transform) % 4;
1895 new_total = (transform - total_transform + 8) % 4;
1896 if((transform >= 4) ^ (total_transform >= 4))
1899 total_transform = new_total;
1901 return total_transform;
1904 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1906 if(transform % 2 == 0)
1909 return core::dimension2d<u32>(dim.Height, dim.Width);
1912 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1914 if(src == NULL || dst == NULL)
1917 core::dimension2d<u32> srcdim = src->getDimension();
1918 core::dimension2d<u32> dstdim = dst->getDimension();
1920 assert(dstdim == imageTransformDimension(transform, srcdim));
1921 assert(transform >= 0 && transform <= 7);
1924 Compute the transformation from source coordinates (sx,sy)
1925 to destination coordinates (dx,dy).
1929 if(transform == 0) // identity
1930 sxn = 0, syn = 2; // sx = dx, sy = dy
1931 else if(transform == 1) // rotate by 90 degrees ccw
1932 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1933 else if(transform == 2) // rotate by 180 degrees
1934 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1935 else if(transform == 3) // rotate by 270 degrees ccw
1936 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1937 else if(transform == 4) // flip x
1938 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1939 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1940 sxn = 2, syn = 0; // sx = dy, sy = dx
1941 else if(transform == 6) // flip y
1942 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1943 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1944 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1946 for(u32 dy=0; dy<dstdim.Height; dy++)
1947 for(u32 dx=0; dx<dstdim.Width; dx++)
1949 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1950 u32 sx = entries[sxn];
1951 u32 sy = entries[syn];
1952 video::SColor c = src->getPixel(sx,sy);
1953 dst->setPixel(dx,dy,c);