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 + m_forcesingle_suffix);
371 // Gets a separate texture atlas pointer
372 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
374 return getTexture(getTextureName(ap.id) + m_forcesingle_suffix);
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;
440 std::string m_forcesingle_suffix;
442 // Queued texture fetches (to be processed by the main thread)
443 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
445 // Textures that have been overwritten with other ones
446 // but can't be deleted because the ITexture* might still be used
447 std::list<video::ITexture*> m_texture_trash;
450 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
452 return new TextureSource(device);
455 TextureSource::TextureSource(IrrlichtDevice *device):
457 m_main_atlas_image(NULL),
458 m_main_atlas_texture(NULL)
462 m_atlaspointer_cache_mutex.Init();
464 m_main_thread = get_current_thread_id();
466 // Add a NULL AtlasPointer as the first index, named ""
467 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
468 m_name_to_id[""] = 0;
471 TextureSource::~TextureSource()
473 video::IVideoDriver* driver = m_device->getVideoDriver();
475 unsigned int textures_before = driver->getTextureCount();
477 for (std::vector<SourceAtlasPointer>::iterator iter =
478 m_atlaspointer_cache.begin(); iter != m_atlaspointer_cache.end();
481 video::ITexture *t = driver->getTexture(iter->name.c_str());
485 driver->removeTexture(t);
487 //cleanup source image
489 iter->atlas_img->drop();
491 m_atlaspointer_cache.clear();
493 for (std::list<video::ITexture*>::iterator iter =
494 m_texture_trash.begin(); iter != m_texture_trash.end();
497 video::ITexture *t = *iter;
499 //cleanup trashed texture
500 driver->removeTexture(t);
503 infostream << "~TextureSource() "<< textures_before << "/"
504 << driver->getTextureCount() << std::endl;
507 u32 TextureSource::getTextureId(const std::string &name)
509 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
513 See if texture already exists
515 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
516 std::map<std::string, u32>::iterator n;
517 n = m_name_to_id.find(name);
518 if(n != m_name_to_id.end())
527 if(get_current_thread_id() == m_main_thread)
529 return getTextureIdDirect(name);
533 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
535 // We're gonna ask the result to be put into here
536 ResultQueue<std::string, u32, u8, u8> result_queue;
538 // Throw a request in
539 m_get_texture_queue.add(name, 0, 0, &result_queue);
541 infostream<<"Waiting for texture from main thread, name=\""
542 <<name<<"\""<<std::endl;
546 // Wait result for a second
547 GetResult<std::string, u32, u8, u8>
548 result = result_queue.pop_front(1000);
550 // Check that at least something worked OK
551 assert(result.key == name);
555 catch(ItemNotFoundException &e)
557 infostream<<"Waiting for texture timed out."<<std::endl;
562 infostream<<"getTextureId(): Failed"<<std::endl;
567 // Overlay image on top of another image (used for cracks)
568 void overlay(video::IImage *image, video::IImage *overlay);
570 // Draw an image on top of an another one, using the alpha channel of the
572 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
573 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
576 void brighten(video::IImage *image);
577 // Parse a transform name
578 u32 parseImageTransform(const std::string& s);
579 // Apply transform to image dimension
580 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
581 // Apply transform to image data
582 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
585 Generate image based on a string like "stone.png" or "[crack0".
586 if baseimg is NULL, it is created. Otherwise stuff is made on it.
588 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
589 IrrlichtDevice *device, SourceImageCache *sourcecache);
592 Generates an image from a full string like
593 "stone.png^mineral_coal.png^[crack0".
595 This is used by buildMainAtlas().
597 video::IImage* generate_image_from_scratch(std::string name,
598 IrrlichtDevice *device, SourceImageCache *sourcecache);
601 This method generates all the textures
603 u32 TextureSource::getTextureIdDirect(const std::string &name)
605 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
607 // Empty name means texture 0
610 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
615 Calling only allowed from main thread
617 if(get_current_thread_id() != m_main_thread)
619 errorstream<<"TextureSource::getTextureIdDirect() "
620 "called not from main thread"<<std::endl;
625 See if texture already exists
628 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
630 std::map<std::string, u32>::iterator n;
631 n = m_name_to_id.find(name);
632 if(n != m_name_to_id.end())
634 /*infostream<<"getTextureIdDirect(): \""<<name
635 <<"\" found in cache"<<std::endl;*/
640 /*infostream<<"getTextureIdDirect(): \""<<name
641 <<"\" NOT found in cache. Creating it."<<std::endl;*/
647 char separator = '^';
650 This is set to the id of the base image.
651 If left 0, there is no base image and a completely new image
654 u32 base_image_id = 0;
656 // Find last meta separator in name
657 s32 last_separator_position = -1;
658 for(s32 i=name.size()-1; i>=0; i--)
660 if(name[i] == separator)
662 last_separator_position = i;
667 If separator was found, construct the base name and make the
668 base image using a recursive call
670 std::string base_image_name;
671 if(last_separator_position != -1)
673 // Construct base name
674 base_image_name = name.substr(0, last_separator_position);
675 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
676 " to get base image of \""<<name<<"\" = \""
677 <<base_image_name<<"\""<<std::endl;*/
678 base_image_id = getTextureIdDirect(base_image_name);
681 //infostream<<"base_image_id="<<base_image_id<<std::endl;
683 video::IVideoDriver* driver = m_device->getVideoDriver();
686 video::ITexture *t = NULL;
689 An image will be built from files and then converted into a texture.
691 video::IImage *baseimg = NULL;
693 // If a base image was found, copy it to baseimg
694 if(base_image_id != 0)
696 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
698 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
700 video::IImage *image = ap.atlas_img;
704 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
705 <<"cache: \""<<base_image_name<<"\""
710 core::dimension2d<u32> dim = ap.intsize;
712 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
714 core::position2d<s32> pos_to(0,0);
715 core::position2d<s32> pos_from = ap.intpos;
719 v2s32(0,0), // position in target
720 core::rect<s32>(pos_from, dim) // from
723 /*infostream<<"getTextureIdDirect(): Loaded \""
724 <<base_image_name<<"\" from image cache"
730 Parse out the last part of the name of the image and act
734 std::string last_part_of_name = name.substr(last_separator_position+1);
735 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
737 // Generate image according to part of name
738 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
740 errorstream<<"getTextureIdDirect(): "
741 "failed to generate \""<<last_part_of_name<<"\""
745 // If no resulting image, print a warning
748 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
749 " create texture \""<<name<<"\""<<std::endl;
754 // Create texture from resulting image
755 t = driver->addTexture(name.c_str(), baseimg);
759 Add texture to caches (add NULL textures too)
762 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
764 u32 id = m_atlaspointer_cache.size();
770 core::dimension2d<u32> baseimg_dim(0,0);
772 baseimg_dim = baseimg->getDimension();
773 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
774 m_atlaspointer_cache.push_back(nap);
775 m_name_to_id[name] = id;
777 /*infostream<<"getTextureIdDirect(): "
778 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
783 std::string TextureSource::getTextureName(u32 id)
785 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
787 if(id >= m_atlaspointer_cache.size())
789 errorstream<<"TextureSource::getTextureName(): id="<<id
790 <<" >= m_atlaspointer_cache.size()="
791 <<m_atlaspointer_cache.size()<<std::endl;
795 return m_atlaspointer_cache[id].name;
799 AtlasPointer TextureSource::getTexture(u32 id)
801 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
803 if(id >= m_atlaspointer_cache.size())
804 return AtlasPointer(0, NULL);
806 return m_atlaspointer_cache[id].a;
809 void TextureSource::updateAP(AtlasPointer &ap)
811 AtlasPointer ap2 = getTexture(ap.id);
815 void TextureSource::processQueue()
820 if(!m_get_texture_queue.empty())
822 GetRequest<std::string, u32, u8, u8>
823 request = m_get_texture_queue.pop();
825 /*infostream<<"TextureSource::processQueue(): "
826 <<"got texture request with "
827 <<"name=\""<<request.key<<"\""
830 GetResult<std::string, u32, u8, u8>
832 result.key = request.key;
833 result.callers = request.callers;
834 result.item = getTextureIdDirect(request.key);
836 request.dest->push_back(result);
840 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
842 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
844 assert(get_current_thread_id() == m_main_thread);
846 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
847 m_source_image_existence.set(name, true);
850 void TextureSource::rebuildImagesAndTextures()
852 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
854 /*// Oh well... just clear everything, they'll load sometime.
855 m_atlaspointer_cache.clear();
856 m_name_to_id.clear();*/
858 video::IVideoDriver* driver = m_device->getVideoDriver();
860 // Remove source images from textures to disable inheriting textures
861 // from existing textures
862 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
863 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
864 sap->atlas_img->drop();
865 sap->atlas_img = NULL;
869 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
870 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
872 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
873 // Create texture from resulting image
874 video::ITexture *t = NULL;
876 t = driver->addTexture(sap->name.c_str(), img);
877 video::ITexture *t_old = sap->a.atlas;
880 sap->a.pos = v2f(0,0);
881 sap->a.size = v2f(1,1);
883 sap->atlas_img = img;
884 sap->intpos = v2s32(0,0);
885 sap->intsize = img->getDimension();
888 m_texture_trash.push_back(t_old);
892 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
894 assert(gamedef->tsrc() == this);
895 INodeDefManager *ndef = gamedef->ndef();
897 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
899 //return; // Disable (for testing)
901 video::IVideoDriver* driver = m_device->getVideoDriver();
904 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
906 // Create an image of the right size
907 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
908 core::dimension2d<u32> atlas_dim(2048,2048);
909 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
910 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
911 video::IImage *atlas_img =
912 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
914 if(atlas_img == NULL)
916 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
917 "image; not building texture atlas."<<std::endl;
922 Grab list of stuff to include in the texture atlas from the
923 main content features
926 std::set<std::string> sourcelist;
928 for(u16 j=0; j<MAX_CONTENT+1; j++)
930 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
932 const ContentFeatures &f = ndef->get(j);
933 for(u32 i=0; i<6; i++)
935 std::string name = f.tiledef[i].name;
936 sourcelist.insert(name);
940 infostream<<"Creating texture atlas out of textures: ";
941 for(std::set<std::string>::iterator
942 i = sourcelist.begin();
943 i != sourcelist.end(); ++i)
945 std::string name = *i;
946 infostream<<"\""<<name<<"\" ";
948 infostream<<std::endl;
950 // Padding to disallow texture bleeding
951 // (16 needed if mipmapping is used; otherwise less will work too)
953 s32 column_padding = 16;
954 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
957 First pass: generate almost everything
959 core::position2d<s32> pos_in_atlas(0,0);
961 pos_in_atlas.X = column_padding;
962 pos_in_atlas.Y = padding;
964 for(std::set<std::string>::iterator
965 i = sourcelist.begin();
966 i != sourcelist.end(); ++i)
968 std::string name = *i;
970 // Generate image by name
971 video::IImage *img2 = generate_image_from_scratch(name, m_device,
975 errorstream<<"TextureSource::buildMainAtlas(): "
976 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
980 core::dimension2d<u32> dim = img2->getDimension();
982 // Don't add to atlas if image is too large
983 core::dimension2d<u32> max_size_in_atlas(64,64);
984 if(dim.Width > max_size_in_atlas.Width
985 || dim.Height > max_size_in_atlas.Height)
987 infostream<<"TextureSource::buildMainAtlas(): Not adding "
988 <<"\""<<name<<"\" because image is large"<<std::endl;
992 // Wrap columns and stop making atlas if atlas is full
993 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
995 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
996 errorstream<<"TextureSource::buildMainAtlas(): "
997 <<"Atlas is full, not adding more textures."
1001 pos_in_atlas.Y = padding;
1002 pos_in_atlas.X += column_width + column_padding*2;
1005 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
1006 <<"\" to texture atlas"<<std::endl;*/
1008 // Tile it a few times in the X direction
1009 u16 xwise_tiling = column_width / dim.Width;
1010 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
1012 for(u32 j=0; j<xwise_tiling; j++)
1014 // Copy the copy to the atlas
1015 /*img2->copyToWithAlpha(atlas_img,
1016 pos_in_atlas + v2s32(j*dim.Width,0),
1017 core::rect<s32>(v2s32(0,0), dim),
1018 video::SColor(255,255,255,255),
1020 img2->copyTo(atlas_img,
1021 pos_in_atlas + v2s32(j*dim.Width,0),
1022 core::rect<s32>(v2s32(0,0), dim),
1026 // Copy the borders a few times to disallow texture bleeding
1027 for(u32 side=0; side<2; side++) // top and bottom
1028 for(s32 y0=0; y0<padding; y0++)
1029 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
1035 dst_y = y0 + pos_in_atlas.Y + dim.Height;
1036 src_y = pos_in_atlas.Y + dim.Height - 1;
1040 dst_y = -y0 + pos_in_atlas.Y-1;
1041 src_y = pos_in_atlas.Y;
1043 s32 x = x0 + pos_in_atlas.X;
1044 video::SColor c = atlas_img->getPixel(x, src_y);
1045 atlas_img->setPixel(x,dst_y,c);
1048 for(u32 side=0; side<2; side++) // left and right
1049 for(s32 x0=0; x0<column_padding; x0++)
1050 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
1056 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
1057 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1061 dst_x = -x0 + pos_in_atlas.X-1;
1062 src_x = pos_in_atlas.X;
1064 s32 y = y0 + pos_in_atlas.Y;
1065 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1067 video::SColor c = atlas_img->getPixel(src_x, src_y);
1068 atlas_img->setPixel(dst_x,dst_y,c);
1074 Add texture to caches
1077 bool reuse_old_id = false;
1078 u32 id = m_atlaspointer_cache.size();
1079 // Check old id without fetching a texture
1080 std::map<std::string, u32>::iterator n;
1081 n = m_name_to_id.find(name);
1082 // If it exists, we will replace the old definition
1083 if(n != m_name_to_id.end()){
1085 reuse_old_id = true;
1086 /*infostream<<"TextureSource::buildMainAtlas(): "
1087 <<"Replacing old AtlasPointer"<<std::endl;*/
1090 // Create AtlasPointer
1091 AtlasPointer ap(id);
1092 ap.atlas = NULL; // Set on the second pass
1093 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1094 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1095 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1096 (float)dim.Width/(float)atlas_dim.Height);
1097 ap.tiled = xwise_tiling;
1099 // Create SourceAtlasPointer and add to containers
1100 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1102 m_atlaspointer_cache[id] = nap;
1104 m_atlaspointer_cache.push_back(nap);
1105 m_name_to_id[name] = id;
1107 // Increment position
1108 pos_in_atlas.Y += dim.Height + padding * 2;
1114 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1118 Second pass: set texture pointer in generated AtlasPointers
1120 for(std::set<std::string>::iterator
1121 i = sourcelist.begin();
1122 i != sourcelist.end(); ++i)
1124 std::string name = *i;
1125 if(m_name_to_id.find(name) == m_name_to_id.end())
1127 u32 id = m_name_to_id[name];
1128 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1129 m_atlaspointer_cache[id].a.atlas = t;
1133 Write image to file so that it can be inspected
1135 /*std::string atlaspath = porting::path_user
1136 + DIR_DELIM + "generated_texture_atlas.png";
1137 infostream<<"Removing and writing texture atlas for inspection to "
1138 <<atlaspath<<std::endl;
1139 fs::RecursiveDelete(atlaspath);
1140 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1142 m_forcesingle_suffix = "^[forcesingle";
1145 video::IImage* generate_image_from_scratch(std::string name,
1146 IrrlichtDevice *device, SourceImageCache *sourcecache)
1148 /*infostream<<"generate_image_from_scratch(): "
1149 "\""<<name<<"\""<<std::endl;*/
1151 video::IVideoDriver* driver = device->getVideoDriver();
1158 video::IImage *baseimg = NULL;
1160 char separator = '^';
1162 // Find last meta separator in name
1163 s32 last_separator_position = name.find_last_of(separator);
1164 //if(last_separator_position == std::npos)
1165 // last_separator_position = -1;
1167 /*infostream<<"generate_image_from_scratch(): "
1168 <<"last_separator_position="<<last_separator_position
1172 If separator was found, construct the base name and make the
1173 base image using a recursive call
1175 std::string base_image_name;
1176 if(last_separator_position != -1)
1178 // Construct base name
1179 base_image_name = name.substr(0, last_separator_position);
1180 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1181 " to get base image of \""<<name<<"\" = \""
1182 <<base_image_name<<"\""<<std::endl;*/
1183 baseimg = generate_image_from_scratch(base_image_name, device,
1188 Parse out the last part of the name of the image and act
1192 std::string last_part_of_name = name.substr(last_separator_position+1);
1193 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1195 // Generate image according to part of name
1196 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1198 errorstream<<"generate_image_from_scratch(): "
1199 "failed to generate \""<<last_part_of_name<<"\""
1207 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1208 IrrlichtDevice *device, SourceImageCache *sourcecache)
1210 video::IVideoDriver* driver = device->getVideoDriver();
1213 // Stuff starting with [ are special commands
1214 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1216 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1220 if(part_of_name != ""){
1221 errorstream<<"generate_image(): Could not load image \""
1222 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1223 errorstream<<"generate_image(): Creating a dummy"
1224 <<" image for \""<<part_of_name<<"\""<<std::endl;
1227 // Just create a dummy image
1228 //core::dimension2d<u32> dim(2,2);
1229 core::dimension2d<u32> dim(1,1);
1230 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1232 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1233 image->setPixel(1,0, video::SColor(255,0,255,0));
1234 image->setPixel(0,1, video::SColor(255,0,0,255));
1235 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1236 image->setPixel(0,0, video::SColor(255,myrand()%256,
1237 myrand()%256,myrand()%256));
1238 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1239 myrand()%256,myrand()%256));
1240 image->setPixel(0,1, video::SColor(255,myrand()%256,
1241 myrand()%256,myrand()%256));
1242 image->setPixel(1,1, video::SColor(255,myrand()%256,
1243 myrand()%256,myrand()%256));*/
1246 // If base image is NULL, load as base.
1249 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1251 Copy it this way to get an alpha channel.
1252 Otherwise images with alpha cannot be blitted on
1253 images that don't have alpha in the original file.
1255 core::dimension2d<u32> dim = image->getDimension();
1256 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1257 image->copyTo(baseimg);
1259 // Else blit on base.
1262 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1263 // Size of the copied area
1264 core::dimension2d<u32> dim = image->getDimension();
1265 //core::dimension2d<u32> dim(16,16);
1266 // Position to copy the blitted to in the base image
1267 core::position2d<s32> pos_to(0,0);
1268 // Position to copy the blitted from in the blitted image
1269 core::position2d<s32> pos_from(0,0);
1271 /*image->copyToWithAlpha(baseimg, pos_to,
1272 core::rect<s32>(pos_from, dim),
1273 video::SColor(255,255,255,255),
1275 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1282 // A special texture modification
1284 /*infostream<<"generate_image(): generating special "
1285 <<"modification \""<<part_of_name<<"\""
1289 This is the simplest of all; it just adds stuff to the
1290 name so that a separate texture is created.
1292 It is used to make textures for stuff that doesn't want
1293 to implement getting the texture from a bigger texture
1296 if(part_of_name == "[forcesingle")
1298 // If base image is NULL, create a random color
1301 core::dimension2d<u32> dim(1,1);
1302 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1304 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1305 myrand()%256,myrand()%256));
1310 Adds a cracking texture
1312 else if(part_of_name.substr(0,6) == "[crack")
1316 errorstream<<"generate_image(): baseimg==NULL "
1317 <<"for part_of_name=\""<<part_of_name
1318 <<"\", cancelling."<<std::endl;
1322 // Crack image number and overlay option
1323 s32 progression = 0;
1324 bool use_overlay = false;
1325 if(part_of_name.substr(6,1) == "o")
1327 progression = stoi(part_of_name.substr(7));
1332 progression = stoi(part_of_name.substr(6));
1333 use_overlay = false;
1336 // Size of the base image
1337 core::dimension2d<u32> dim_base = baseimg->getDimension();
1342 It is an image with a number of cracking stages
1345 video::IImage *img_crack = sourcecache->getOrLoad(
1346 "crack_anylength.png", device);
1348 if(img_crack && progression >= 0)
1350 // Dimension of original image
1351 core::dimension2d<u32> dim_crack
1352 = img_crack->getDimension();
1353 // Count of crack stages
1354 s32 crack_count = dim_crack.Height / dim_crack.Width;
1355 // Limit progression
1356 if(progression > crack_count-1)
1357 progression = crack_count-1;
1358 // Dimension of a single crack stage
1359 core::dimension2d<u32> dim_crack_cropped(
1363 // Create cropped and scaled crack images
1364 video::IImage *img_crack_cropped = driver->createImage(
1365 video::ECF_A8R8G8B8, dim_crack_cropped);
1366 video::IImage *img_crack_scaled = driver->createImage(
1367 video::ECF_A8R8G8B8, dim_base);
1369 if(img_crack_cropped && img_crack_scaled)
1372 v2s32 pos_crack(0, progression*dim_crack.Width);
1373 img_crack->copyTo(img_crack_cropped,
1375 core::rect<s32>(pos_crack, dim_crack_cropped));
1376 // Scale crack image by copying
1377 img_crack_cropped->copyToScaling(img_crack_scaled);
1378 // Copy or overlay crack image
1381 overlay(baseimg, img_crack_scaled);
1385 /*img_crack_scaled->copyToWithAlpha(
1388 core::rect<s32>(v2s32(0,0), dim_base),
1389 video::SColor(255,255,255,255));*/
1390 blit_with_alpha(img_crack_scaled, baseimg,
1391 v2s32(0,0), v2s32(0,0), dim_base);
1395 if(img_crack_scaled)
1396 img_crack_scaled->drop();
1398 if(img_crack_cropped)
1399 img_crack_cropped->drop();
1405 [combine:WxH:X,Y=filename:X,Y=filename2
1406 Creates a bigger texture from an amount of smaller ones
1408 else if(part_of_name.substr(0,8) == "[combine")
1410 Strfnd sf(part_of_name);
1412 u32 w0 = stoi(sf.next("x"));
1413 u32 h0 = stoi(sf.next(":"));
1414 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1415 core::dimension2d<u32> dim(w0,h0);
1418 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1419 baseimg->fill(video::SColor(0,0,0,0));
1421 while(sf.atend() == false)
1423 u32 x = stoi(sf.next(","));
1424 u32 y = stoi(sf.next("="));
1425 std::string filename = sf.next(":");
1426 infostream<<"Adding \""<<filename
1427 <<"\" to combined ("<<x<<","<<y<<")"
1429 video::IImage *img = sourcecache->getOrLoad(filename, device);
1432 core::dimension2d<u32> dim = img->getDimension();
1433 infostream<<"Size "<<dim.Width
1434 <<"x"<<dim.Height<<std::endl;
1435 core::position2d<s32> pos_base(x, y);
1436 video::IImage *img2 =
1437 driver->createImage(video::ECF_A8R8G8B8, dim);
1440 /*img2->copyToWithAlpha(baseimg, pos_base,
1441 core::rect<s32>(v2s32(0,0), dim),
1442 video::SColor(255,255,255,255),
1444 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1449 infostream<<"img==NULL"<<std::endl;
1456 else if(part_of_name.substr(0,9) == "[brighten")
1460 errorstream<<"generate_image(): baseimg==NULL "
1461 <<"for part_of_name=\""<<part_of_name
1462 <<"\", cancelling."<<std::endl;
1470 Make image completely opaque.
1471 Used for the leaves texture when in old leaves mode, so
1472 that the transparent parts don't look completely black
1473 when simple alpha channel is used for rendering.
1475 else if(part_of_name.substr(0,8) == "[noalpha")
1479 errorstream<<"generate_image(): baseimg==NULL "
1480 <<"for part_of_name=\""<<part_of_name
1481 <<"\", cancelling."<<std::endl;
1485 core::dimension2d<u32> dim = baseimg->getDimension();
1487 // Set alpha to full
1488 for(u32 y=0; y<dim.Height; y++)
1489 for(u32 x=0; x<dim.Width; x++)
1491 video::SColor c = baseimg->getPixel(x,y);
1493 baseimg->setPixel(x,y,c);
1498 Convert one color to transparent.
1500 else if(part_of_name.substr(0,11) == "[makealpha:")
1504 errorstream<<"generate_image(): baseimg==NULL "
1505 <<"for part_of_name=\""<<part_of_name
1506 <<"\", cancelling."<<std::endl;
1510 Strfnd sf(part_of_name.substr(11));
1511 u32 r1 = stoi(sf.next(","));
1512 u32 g1 = stoi(sf.next(","));
1513 u32 b1 = stoi(sf.next(""));
1514 std::string filename = sf.next("");
1516 core::dimension2d<u32> dim = baseimg->getDimension();
1518 /*video::IImage *oldbaseimg = baseimg;
1519 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1520 oldbaseimg->copyTo(baseimg);
1521 oldbaseimg->drop();*/
1523 // Set alpha to full
1524 for(u32 y=0; y<dim.Height; y++)
1525 for(u32 x=0; x<dim.Width; x++)
1527 video::SColor c = baseimg->getPixel(x,y);
1529 u32 g = c.getGreen();
1530 u32 b = c.getBlue();
1531 if(!(r == r1 && g == g1 && b == b1))
1534 baseimg->setPixel(x,y,c);
1539 Rotates and/or flips the image.
1541 N can be a number (between 0 and 7) or a transform name.
1542 Rotations are counter-clockwise.
1544 1 R90 rotate by 90 degrees
1545 2 R180 rotate by 180 degrees
1546 3 R270 rotate by 270 degrees
1548 5 FXR90 flip X then rotate by 90 degrees
1550 7 FYR90 flip Y then rotate by 90 degrees
1552 Note: Transform names can be concatenated to produce
1553 their product (applies the first then the second).
1554 The resulting transform will be equivalent to one of the
1555 eight existing ones, though (see: dihedral group).
1557 else if(part_of_name.substr(0,10) == "[transform")
1561 errorstream<<"generate_image(): baseimg==NULL "
1562 <<"for part_of_name=\""<<part_of_name
1563 <<"\", cancelling."<<std::endl;
1567 u32 transform = parseImageTransform(part_of_name.substr(10));
1568 core::dimension2d<u32> dim = imageTransformDimension(
1569 transform, baseimg->getDimension());
1570 video::IImage *image = driver->createImage(
1571 baseimg->getColorFormat(), dim);
1573 imageTransform(transform, baseimg, image);
1578 [inventorycube{topimage{leftimage{rightimage
1579 In every subimage, replace ^ with &.
1580 Create an "inventory cube".
1581 NOTE: This should be used only on its own.
1582 Example (a grass block (not actually used in game):
1583 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1585 else if(part_of_name.substr(0,14) == "[inventorycube")
1589 errorstream<<"generate_image(): baseimg!=NULL "
1590 <<"for part_of_name=\""<<part_of_name
1591 <<"\", cancelling."<<std::endl;
1595 str_replace_char(part_of_name, '&', '^');
1596 Strfnd sf(part_of_name);
1598 std::string imagename_top = sf.next("{");
1599 std::string imagename_left = sf.next("{");
1600 std::string imagename_right = sf.next("{");
1602 // Generate images for the faces of the cube
1603 video::IImage *img_top = generate_image_from_scratch(
1604 imagename_top, device, sourcecache);
1605 video::IImage *img_left = generate_image_from_scratch(
1606 imagename_left, device, sourcecache);
1607 video::IImage *img_right = generate_image_from_scratch(
1608 imagename_right, device, sourcecache);
1609 assert(img_top && img_left && img_right);
1611 // Create textures from images
1612 video::ITexture *texture_top = driver->addTexture(
1613 (imagename_top + "__temp__").c_str(), img_top);
1614 video::ITexture *texture_left = driver->addTexture(
1615 (imagename_left + "__temp__").c_str(), img_left);
1616 video::ITexture *texture_right = driver->addTexture(
1617 (imagename_right + "__temp__").c_str(), img_right);
1618 assert(texture_top && texture_left && texture_right);
1626 Draw a cube mesh into a render target texture
1628 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1629 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1630 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1631 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1632 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1633 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1634 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1635 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1637 core::dimension2d<u32> dim(64,64);
1638 std::string rtt_texture_name = part_of_name + "_RTT";
1640 v3f camera_position(0, 1.0, -1.5);
1641 camera_position.rotateXZBy(45);
1642 v3f camera_lookat(0, 0, 0);
1643 core::CMatrix4<f32> camera_projection_matrix;
1644 // Set orthogonal projection
1645 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1646 1.65, 1.65, 0, 100);
1648 video::SColorf ambient_light(0.2,0.2,0.2);
1649 v3f light_position(10, 100, -50);
1650 video::SColorf light_color(0.5,0.5,0.5);
1651 f32 light_radius = 1000;
1653 video::ITexture *rtt = generateTextureFromMesh(
1654 cube, device, dim, rtt_texture_name,
1657 camera_projection_matrix,
1666 // Free textures of images
1667 driver->removeTexture(texture_top);
1668 driver->removeTexture(texture_left);
1669 driver->removeTexture(texture_right);
1673 baseimg = generate_image_from_scratch(
1674 imagename_top, device, sourcecache);
1678 // Create image of render target
1679 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1683 driver->removeTexture(rtt);
1685 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1689 image->copyTo(baseimg);
1694 [lowpart:percent:filename
1695 Adds the lower part of a texture
1697 else if(part_of_name.substr(0,9) == "[lowpart:")
1699 Strfnd sf(part_of_name);
1701 u32 percent = stoi(sf.next(":"));
1702 std::string filename = sf.next(":");
1703 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1706 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1707 video::IImage *img = sourcecache->getOrLoad(filename, device);
1710 core::dimension2d<u32> dim = img->getDimension();
1711 core::position2d<s32> pos_base(0, 0);
1712 video::IImage *img2 =
1713 driver->createImage(video::ECF_A8R8G8B8, dim);
1716 core::position2d<s32> clippos(0, 0);
1717 clippos.Y = dim.Height * (100-percent) / 100;
1718 core::dimension2d<u32> clipdim = dim;
1719 clipdim.Height = clipdim.Height * percent / 100 + 1;
1720 core::rect<s32> cliprect(clippos, clipdim);
1721 img2->copyToWithAlpha(baseimg, pos_base,
1722 core::rect<s32>(v2s32(0,0), dim),
1723 video::SColor(255,255,255,255),
1730 Crops a frame of a vertical animation.
1731 N = frame count, I = frame index
1733 else if(part_of_name.substr(0,15) == "[verticalframe:")
1735 Strfnd sf(part_of_name);
1737 u32 frame_count = stoi(sf.next(":"));
1738 u32 frame_index = stoi(sf.next(":"));
1740 if(baseimg == NULL){
1741 errorstream<<"generate_image(): baseimg!=NULL "
1742 <<"for part_of_name=\""<<part_of_name
1743 <<"\", cancelling."<<std::endl;
1747 v2u32 frame_size = baseimg->getDimension();
1748 frame_size.Y /= frame_count;
1750 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1753 errorstream<<"generate_image(): Could not create image "
1754 <<"for part_of_name=\""<<part_of_name
1755 <<"\", cancelling."<<std::endl;
1759 // Fill target image with transparency
1760 img->fill(video::SColor(0,0,0,0));
1762 core::dimension2d<u32> dim = frame_size;
1763 core::position2d<s32> pos_dst(0, 0);
1764 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1765 baseimg->copyToWithAlpha(img, pos_dst,
1766 core::rect<s32>(pos_src, dim),
1767 video::SColor(255,255,255,255),
1775 errorstream<<"generate_image(): Invalid "
1776 " modification: \""<<part_of_name<<"\""<<std::endl;
1783 void overlay(video::IImage *image, video::IImage *overlay)
1786 Copy overlay to image, taking alpha into account.
1787 Where image is transparent, don't copy from overlay.
1788 Images sizes must be identical.
1790 if(image == NULL || overlay == NULL)
1793 core::dimension2d<u32> dim = image->getDimension();
1794 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1795 assert(dim == dim_overlay);
1797 for(u32 y=0; y<dim.Height; y++)
1798 for(u32 x=0; x<dim.Width; x++)
1800 video::SColor c1 = image->getPixel(x,y);
1801 video::SColor c2 = overlay->getPixel(x,y);
1802 u32 a1 = c1.getAlpha();
1803 u32 a2 = c2.getAlpha();
1804 if(a1 == 255 && a2 != 0)
1806 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1807 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1808 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1810 image->setPixel(x,y,c1);
1815 Draw an image on top of an another one, using the alpha channel of the
1818 This exists because IImage::copyToWithAlpha() doesn't seem to always
1821 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1822 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1824 for(u32 y0=0; y0<size.Y; y0++)
1825 for(u32 x0=0; x0<size.X; x0++)
1827 s32 src_x = src_pos.X + x0;
1828 s32 src_y = src_pos.Y + y0;
1829 s32 dst_x = dst_pos.X + x0;
1830 s32 dst_y = dst_pos.Y + y0;
1831 video::SColor src_c = src->getPixel(src_x, src_y);
1832 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1833 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1834 dst->setPixel(dst_x, dst_y, dst_c);
1838 void brighten(video::IImage *image)
1843 core::dimension2d<u32> dim = image->getDimension();
1845 for(u32 y=0; y<dim.Height; y++)
1846 for(u32 x=0; x<dim.Width; x++)
1848 video::SColor c = image->getPixel(x,y);
1849 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1850 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1851 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1852 image->setPixel(x,y,c);
1856 u32 parseImageTransform(const std::string& s)
1858 int total_transform = 0;
1860 std::string transform_names[8];
1861 transform_names[0] = "i";
1862 transform_names[1] = "r90";
1863 transform_names[2] = "r180";
1864 transform_names[3] = "r270";
1865 transform_names[4] = "fx";
1866 transform_names[6] = "fy";
1868 std::size_t pos = 0;
1869 while(pos < s.size())
1872 for(int i = 0; i <= 7; ++i)
1874 const std::string &name_i = transform_names[i];
1876 if(s[pos] == ('0' + i))
1882 else if(!(name_i.empty()) &&
1883 lowercase(s.substr(pos, name_i.size())) == name_i)
1886 pos += name_i.size();
1893 // Multiply total_transform and transform in the group D4
1896 new_total = (transform + total_transform) % 4;
1898 new_total = (transform - total_transform + 8) % 4;
1899 if((transform >= 4) ^ (total_transform >= 4))
1902 total_transform = new_total;
1904 return total_transform;
1907 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1909 if(transform % 2 == 0)
1912 return core::dimension2d<u32>(dim.Height, dim.Width);
1915 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1917 if(src == NULL || dst == NULL)
1920 core::dimension2d<u32> srcdim = src->getDimension();
1921 core::dimension2d<u32> dstdim = dst->getDimension();
1923 assert(dstdim == imageTransformDimension(transform, srcdim));
1924 assert(transform >= 0 && transform <= 7);
1927 Compute the transformation from source coordinates (sx,sy)
1928 to destination coordinates (dx,dy).
1932 if(transform == 0) // identity
1933 sxn = 0, syn = 2; // sx = dx, sy = dy
1934 else if(transform == 1) // rotate by 90 degrees ccw
1935 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1936 else if(transform == 2) // rotate by 180 degrees
1937 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1938 else if(transform == 3) // rotate by 270 degrees ccw
1939 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1940 else if(transform == 4) // flip x
1941 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1942 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1943 sxn = 2, syn = 0; // sx = dy, sy = dx
1944 else if(transform == 6) // flip y
1945 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1946 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1947 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1949 for(u32 dy=0; dy<dstdim.Height; dy++)
1950 for(u32 dx=0; dx<dstdim.Width; dx++)
1952 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1953 u32 sx = entries[sxn];
1954 u32 sy = entries[syn];
1955 video::SColor c = src->getPixel(sx,sy);
1956 dst->setPixel(dx,dy,c);