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;
445 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
447 return new TextureSource(device);
450 TextureSource::TextureSource(IrrlichtDevice *device):
452 m_main_atlas_image(NULL),
453 m_main_atlas_texture(NULL)
457 m_atlaspointer_cache_mutex.Init();
459 m_main_thread = get_current_thread_id();
461 // Add a NULL AtlasPointer as the first index, named ""
462 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
463 m_name_to_id[""] = 0;
466 TextureSource::~TextureSource()
468 video::IVideoDriver* driver = m_device->getVideoDriver();
470 unsigned int textures_before = driver->getTextureCount();
472 for (std::vector<SourceAtlasPointer>::iterator iter =
473 m_atlaspointer_cache.begin(); iter != m_atlaspointer_cache.end();
476 video::ITexture *t = driver->getTexture(iter->name.c_str());
480 driver->removeTexture(t);
482 //cleanup source image
484 iter->atlas_img->drop();
486 m_atlaspointer_cache.clear();
488 infostream << "~TextureSource() "<< textures_before << "/"
489 << driver->getTextureCount() << std::endl;
492 u32 TextureSource::getTextureId(const std::string &name)
494 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
498 See if texture already exists
500 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
501 std::map<std::string, u32>::iterator n;
502 n = m_name_to_id.find(name);
503 if(n != m_name_to_id.end())
512 if(get_current_thread_id() == m_main_thread)
514 return getTextureIdDirect(name);
518 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
520 // We're gonna ask the result to be put into here
521 ResultQueue<std::string, u32, u8, u8> result_queue;
523 // Throw a request in
524 m_get_texture_queue.add(name, 0, 0, &result_queue);
526 infostream<<"Waiting for texture from main thread, name=\""
527 <<name<<"\""<<std::endl;
531 // Wait result for a second
532 GetResult<std::string, u32, u8, u8>
533 result = result_queue.pop_front(1000);
535 // Check that at least something worked OK
536 assert(result.key == name);
540 catch(ItemNotFoundException &e)
542 infostream<<"Waiting for texture timed out."<<std::endl;
547 infostream<<"getTextureId(): Failed"<<std::endl;
552 // Overlay image on top of another image (used for cracks)
553 void overlay(video::IImage *image, video::IImage *overlay);
555 // Draw an image on top of an another one, using the alpha channel of the
557 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
558 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
561 void brighten(video::IImage *image);
562 // Parse a transform name
563 u32 parseImageTransform(const std::string& s);
564 // Apply transform to image dimension
565 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
566 // Apply transform to image data
567 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
570 Generate image based on a string like "stone.png" or "[crack0".
571 if baseimg is NULL, it is created. Otherwise stuff is made on it.
573 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
574 IrrlichtDevice *device, SourceImageCache *sourcecache);
577 Generates an image from a full string like
578 "stone.png^mineral_coal.png^[crack0".
580 This is used by buildMainAtlas().
582 video::IImage* generate_image_from_scratch(std::string name,
583 IrrlichtDevice *device, SourceImageCache *sourcecache);
586 This method generates all the textures
588 u32 TextureSource::getTextureIdDirect(const std::string &name)
590 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
592 // Empty name means texture 0
595 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
600 Calling only allowed from main thread
602 if(get_current_thread_id() != m_main_thread)
604 errorstream<<"TextureSource::getTextureIdDirect() "
605 "called not from main thread"<<std::endl;
610 See if texture already exists
613 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
615 std::map<std::string, u32>::iterator n;
616 n = m_name_to_id.find(name);
617 if(n != m_name_to_id.end())
619 /*infostream<<"getTextureIdDirect(): \""<<name
620 <<"\" found in cache"<<std::endl;*/
625 /*infostream<<"getTextureIdDirect(): \""<<name
626 <<"\" NOT found in cache. Creating it."<<std::endl;*/
632 char separator = '^';
635 This is set to the id of the base image.
636 If left 0, there is no base image and a completely new image
639 u32 base_image_id = 0;
641 // Find last meta separator in name
642 s32 last_separator_position = -1;
643 for(s32 i=name.size()-1; i>=0; i--)
645 if(name[i] == separator)
647 last_separator_position = i;
652 If separator was found, construct the base name and make the
653 base image using a recursive call
655 std::string base_image_name;
656 if(last_separator_position != -1)
658 // Construct base name
659 base_image_name = name.substr(0, last_separator_position);
660 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
661 " to get base image of \""<<name<<"\" = \""
662 <<base_image_name<<"\""<<std::endl;*/
663 base_image_id = getTextureIdDirect(base_image_name);
666 //infostream<<"base_image_id="<<base_image_id<<std::endl;
668 video::IVideoDriver* driver = m_device->getVideoDriver();
671 video::ITexture *t = NULL;
674 An image will be built from files and then converted into a texture.
676 video::IImage *baseimg = NULL;
678 // If a base image was found, copy it to baseimg
679 if(base_image_id != 0)
681 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
683 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
685 video::IImage *image = ap.atlas_img;
689 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
690 <<"cache: \""<<base_image_name<<"\""
695 core::dimension2d<u32> dim = ap.intsize;
697 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
699 core::position2d<s32> pos_to(0,0);
700 core::position2d<s32> pos_from = ap.intpos;
704 v2s32(0,0), // position in target
705 core::rect<s32>(pos_from, dim) // from
708 /*infostream<<"getTextureIdDirect(): Loaded \""
709 <<base_image_name<<"\" from image cache"
715 Parse out the last part of the name of the image and act
719 std::string last_part_of_name = name.substr(last_separator_position+1);
720 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
722 // Generate image according to part of name
723 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
725 errorstream<<"getTextureIdDirect(): "
726 "failed to generate \""<<last_part_of_name<<"\""
730 // If no resulting image, print a warning
733 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
734 " create texture \""<<name<<"\""<<std::endl;
739 // Create texture from resulting image
740 t = driver->addTexture(name.c_str(), baseimg);
744 Add texture to caches (add NULL textures too)
747 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
749 u32 id = m_atlaspointer_cache.size();
755 core::dimension2d<u32> baseimg_dim(0,0);
757 baseimg_dim = baseimg->getDimension();
758 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
759 m_atlaspointer_cache.push_back(nap);
760 m_name_to_id[name] = id;
762 /*infostream<<"getTextureIdDirect(): "
763 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
768 std::string TextureSource::getTextureName(u32 id)
770 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
772 if(id >= m_atlaspointer_cache.size())
774 errorstream<<"TextureSource::getTextureName(): id="<<id
775 <<" >= m_atlaspointer_cache.size()="
776 <<m_atlaspointer_cache.size()<<std::endl;
780 return m_atlaspointer_cache[id].name;
784 AtlasPointer TextureSource::getTexture(u32 id)
786 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
788 if(id >= m_atlaspointer_cache.size())
789 return AtlasPointer(0, NULL);
791 return m_atlaspointer_cache[id].a;
794 void TextureSource::updateAP(AtlasPointer &ap)
796 AtlasPointer ap2 = getTexture(ap.id);
800 void TextureSource::processQueue()
805 if(!m_get_texture_queue.empty())
807 GetRequest<std::string, u32, u8, u8>
808 request = m_get_texture_queue.pop();
810 /*infostream<<"TextureSource::processQueue(): "
811 <<"got texture request with "
812 <<"name=\""<<request.key<<"\""
815 GetResult<std::string, u32, u8, u8>
817 result.key = request.key;
818 result.callers = request.callers;
819 result.item = getTextureIdDirect(request.key);
821 request.dest->push_back(result);
825 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
827 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
829 assert(get_current_thread_id() == m_main_thread);
831 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
832 m_source_image_existence.set(name, true);
835 void TextureSource::rebuildImagesAndTextures()
837 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
839 /*// Oh well... just clear everything, they'll load sometime.
840 m_atlaspointer_cache.clear();
841 m_name_to_id.clear();*/
843 video::IVideoDriver* driver = m_device->getVideoDriver();
845 // Remove source images from textures to disable inheriting textures
846 // from existing textures
847 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
848 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
849 sap->atlas_img->drop();
850 sap->atlas_img = NULL;
854 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
855 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
857 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
858 // Create texture from resulting image
859 video::ITexture *t = NULL;
861 t = driver->addTexture(sap->name.c_str(), img);
862 video::ITexture *t_old = sap->a.atlas;
865 sap->a.pos = v2f(0,0);
866 sap->a.size = v2f(1,1);
868 sap->atlas_img = img;
869 sap->intpos = v2s32(0,0);
870 sap->intsize = img->getDimension();
873 driver->removeTexture(t_old);
877 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
879 assert(gamedef->tsrc() == this);
880 INodeDefManager *ndef = gamedef->ndef();
882 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
884 //return; // Disable (for testing)
886 video::IVideoDriver* driver = m_device->getVideoDriver();
889 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
891 // Create an image of the right size
892 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
893 core::dimension2d<u32> atlas_dim(2048,2048);
894 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
895 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
896 video::IImage *atlas_img =
897 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
899 if(atlas_img == NULL)
901 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
902 "image; not building texture atlas."<<std::endl;
907 Grab list of stuff to include in the texture atlas from the
908 main content features
911 std::set<std::string> sourcelist;
913 for(u16 j=0; j<MAX_CONTENT+1; j++)
915 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
917 const ContentFeatures &f = ndef->get(j);
918 for(u32 i=0; i<6; i++)
920 std::string name = f.tiledef[i].name;
921 sourcelist.insert(name);
925 infostream<<"Creating texture atlas out of textures: ";
926 for(std::set<std::string>::iterator
927 i = sourcelist.begin();
928 i != sourcelist.end(); ++i)
930 std::string name = *i;
931 infostream<<"\""<<name<<"\" ";
933 infostream<<std::endl;
935 // Padding to disallow texture bleeding
936 // (16 needed if mipmapping is used; otherwise less will work too)
938 s32 column_padding = 16;
939 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
942 First pass: generate almost everything
944 core::position2d<s32> pos_in_atlas(0,0);
946 pos_in_atlas.X = column_padding;
947 pos_in_atlas.Y = padding;
949 for(std::set<std::string>::iterator
950 i = sourcelist.begin();
951 i != sourcelist.end(); ++i)
953 std::string name = *i;
955 // Generate image by name
956 video::IImage *img2 = generate_image_from_scratch(name, m_device,
960 errorstream<<"TextureSource::buildMainAtlas(): "
961 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
965 core::dimension2d<u32> dim = img2->getDimension();
967 // Don't add to atlas if image is too large
968 core::dimension2d<u32> max_size_in_atlas(64,64);
969 if(dim.Width > max_size_in_atlas.Width
970 || dim.Height > max_size_in_atlas.Height)
972 infostream<<"TextureSource::buildMainAtlas(): Not adding "
973 <<"\""<<name<<"\" because image is large"<<std::endl;
977 // Wrap columns and stop making atlas if atlas is full
978 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
980 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
981 errorstream<<"TextureSource::buildMainAtlas(): "
982 <<"Atlas is full, not adding more textures."
986 pos_in_atlas.Y = padding;
987 pos_in_atlas.X += column_width + column_padding*2;
990 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
991 <<"\" to texture atlas"<<std::endl;*/
993 // Tile it a few times in the X direction
994 u16 xwise_tiling = column_width / dim.Width;
995 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
997 for(u32 j=0; j<xwise_tiling; j++)
999 // Copy the copy to the atlas
1000 /*img2->copyToWithAlpha(atlas_img,
1001 pos_in_atlas + v2s32(j*dim.Width,0),
1002 core::rect<s32>(v2s32(0,0), dim),
1003 video::SColor(255,255,255,255),
1005 img2->copyTo(atlas_img,
1006 pos_in_atlas + v2s32(j*dim.Width,0),
1007 core::rect<s32>(v2s32(0,0), dim),
1011 // Copy the borders a few times to disallow texture bleeding
1012 for(u32 side=0; side<2; side++) // top and bottom
1013 for(s32 y0=0; y0<padding; y0++)
1014 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
1020 dst_y = y0 + pos_in_atlas.Y + dim.Height;
1021 src_y = pos_in_atlas.Y + dim.Height - 1;
1025 dst_y = -y0 + pos_in_atlas.Y-1;
1026 src_y = pos_in_atlas.Y;
1028 s32 x = x0 + pos_in_atlas.X;
1029 video::SColor c = atlas_img->getPixel(x, src_y);
1030 atlas_img->setPixel(x,dst_y,c);
1033 for(u32 side=0; side<2; side++) // left and right
1034 for(s32 x0=0; x0<column_padding; x0++)
1035 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
1041 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
1042 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1046 dst_x = -x0 + pos_in_atlas.X-1;
1047 src_x = pos_in_atlas.X;
1049 s32 y = y0 + pos_in_atlas.Y;
1050 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1052 video::SColor c = atlas_img->getPixel(src_x, src_y);
1053 atlas_img->setPixel(dst_x,dst_y,c);
1059 Add texture to caches
1062 bool reuse_old_id = false;
1063 u32 id = m_atlaspointer_cache.size();
1064 // Check old id without fetching a texture
1065 std::map<std::string, u32>::iterator n;
1066 n = m_name_to_id.find(name);
1067 // If it exists, we will replace the old definition
1068 if(n != m_name_to_id.end()){
1070 reuse_old_id = true;
1071 /*infostream<<"TextureSource::buildMainAtlas(): "
1072 <<"Replacing old AtlasPointer"<<std::endl;*/
1075 // Create AtlasPointer
1076 AtlasPointer ap(id);
1077 ap.atlas = NULL; // Set on the second pass
1078 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1079 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1080 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1081 (float)dim.Width/(float)atlas_dim.Height);
1082 ap.tiled = xwise_tiling;
1084 // Create SourceAtlasPointer and add to containers
1085 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1087 m_atlaspointer_cache[id] = nap;
1089 m_atlaspointer_cache.push_back(nap);
1090 m_name_to_id[name] = id;
1092 // Increment position
1093 pos_in_atlas.Y += dim.Height + padding * 2;
1099 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1103 Second pass: set texture pointer in generated AtlasPointers
1105 for(std::set<std::string>::iterator
1106 i = sourcelist.begin();
1107 i != sourcelist.end(); ++i)
1109 std::string name = *i;
1110 if(m_name_to_id.find(name) == m_name_to_id.end())
1112 u32 id = m_name_to_id[name];
1113 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1114 m_atlaspointer_cache[id].a.atlas = t;
1118 Write image to file so that it can be inspected
1120 /*std::string atlaspath = porting::path_user
1121 + DIR_DELIM + "generated_texture_atlas.png";
1122 infostream<<"Removing and writing texture atlas for inspection to "
1123 <<atlaspath<<std::endl;
1124 fs::RecursiveDelete(atlaspath);
1125 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1128 video::IImage* generate_image_from_scratch(std::string name,
1129 IrrlichtDevice *device, SourceImageCache *sourcecache)
1131 /*infostream<<"generate_image_from_scratch(): "
1132 "\""<<name<<"\""<<std::endl;*/
1134 video::IVideoDriver* driver = device->getVideoDriver();
1141 video::IImage *baseimg = NULL;
1143 char separator = '^';
1145 // Find last meta separator in name
1146 s32 last_separator_position = name.find_last_of(separator);
1147 //if(last_separator_position == std::npos)
1148 // last_separator_position = -1;
1150 /*infostream<<"generate_image_from_scratch(): "
1151 <<"last_separator_position="<<last_separator_position
1155 If separator was found, construct the base name and make the
1156 base image using a recursive call
1158 std::string base_image_name;
1159 if(last_separator_position != -1)
1161 // Construct base name
1162 base_image_name = name.substr(0, last_separator_position);
1163 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1164 " to get base image of \""<<name<<"\" = \""
1165 <<base_image_name<<"\""<<std::endl;*/
1166 baseimg = generate_image_from_scratch(base_image_name, device,
1171 Parse out the last part of the name of the image and act
1175 std::string last_part_of_name = name.substr(last_separator_position+1);
1176 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1178 // Generate image according to part of name
1179 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1181 errorstream<<"generate_image_from_scratch(): "
1182 "failed to generate \""<<last_part_of_name<<"\""
1190 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1191 IrrlichtDevice *device, SourceImageCache *sourcecache)
1193 video::IVideoDriver* driver = device->getVideoDriver();
1196 // Stuff starting with [ are special commands
1197 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1199 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1203 if(part_of_name != ""){
1204 errorstream<<"generate_image(): Could not load image \""
1205 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1206 errorstream<<"generate_image(): Creating a dummy"
1207 <<" image for \""<<part_of_name<<"\""<<std::endl;
1210 // Just create a dummy image
1211 //core::dimension2d<u32> dim(2,2);
1212 core::dimension2d<u32> dim(1,1);
1213 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1215 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1216 image->setPixel(1,0, video::SColor(255,0,255,0));
1217 image->setPixel(0,1, video::SColor(255,0,0,255));
1218 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1219 image->setPixel(0,0, video::SColor(255,myrand()%256,
1220 myrand()%256,myrand()%256));
1221 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1222 myrand()%256,myrand()%256));
1223 image->setPixel(0,1, video::SColor(255,myrand()%256,
1224 myrand()%256,myrand()%256));
1225 image->setPixel(1,1, video::SColor(255,myrand()%256,
1226 myrand()%256,myrand()%256));*/
1229 // If base image is NULL, load as base.
1232 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1234 Copy it this way to get an alpha channel.
1235 Otherwise images with alpha cannot be blitted on
1236 images that don't have alpha in the original file.
1238 core::dimension2d<u32> dim = image->getDimension();
1239 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1240 image->copyTo(baseimg);
1242 // Else blit on base.
1245 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1246 // Size of the copied area
1247 core::dimension2d<u32> dim = image->getDimension();
1248 //core::dimension2d<u32> dim(16,16);
1249 // Position to copy the blitted to in the base image
1250 core::position2d<s32> pos_to(0,0);
1251 // Position to copy the blitted from in the blitted image
1252 core::position2d<s32> pos_from(0,0);
1254 /*image->copyToWithAlpha(baseimg, pos_to,
1255 core::rect<s32>(pos_from, dim),
1256 video::SColor(255,255,255,255),
1258 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1265 // A special texture modification
1267 /*infostream<<"generate_image(): generating special "
1268 <<"modification \""<<part_of_name<<"\""
1272 This is the simplest of all; it just adds stuff to the
1273 name so that a separate texture is created.
1275 It is used to make textures for stuff that doesn't want
1276 to implement getting the texture from a bigger texture
1279 if(part_of_name == "[forcesingle")
1281 // If base image is NULL, create a random color
1284 core::dimension2d<u32> dim(1,1);
1285 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1287 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1288 myrand()%256,myrand()%256));
1293 Adds a cracking texture
1295 else if(part_of_name.substr(0,6) == "[crack")
1299 errorstream<<"generate_image(): baseimg==NULL "
1300 <<"for part_of_name=\""<<part_of_name
1301 <<"\", cancelling."<<std::endl;
1305 // Crack image number and overlay option
1306 s32 progression = 0;
1307 bool use_overlay = false;
1308 if(part_of_name.substr(6,1) == "o")
1310 progression = stoi(part_of_name.substr(7));
1315 progression = stoi(part_of_name.substr(6));
1316 use_overlay = false;
1319 // Size of the base image
1320 core::dimension2d<u32> dim_base = baseimg->getDimension();
1325 It is an image with a number of cracking stages
1328 video::IImage *img_crack = sourcecache->getOrLoad(
1329 "crack_anylength.png", device);
1331 if(img_crack && progression >= 0)
1333 // Dimension of original image
1334 core::dimension2d<u32> dim_crack
1335 = img_crack->getDimension();
1336 // Count of crack stages
1337 s32 crack_count = dim_crack.Height / dim_crack.Width;
1338 // Limit progression
1339 if(progression > crack_count-1)
1340 progression = crack_count-1;
1341 // Dimension of a single crack stage
1342 core::dimension2d<u32> dim_crack_cropped(
1346 // Create cropped and scaled crack images
1347 video::IImage *img_crack_cropped = driver->createImage(
1348 video::ECF_A8R8G8B8, dim_crack_cropped);
1349 video::IImage *img_crack_scaled = driver->createImage(
1350 video::ECF_A8R8G8B8, dim_base);
1352 if(img_crack_cropped && img_crack_scaled)
1355 v2s32 pos_crack(0, progression*dim_crack.Width);
1356 img_crack->copyTo(img_crack_cropped,
1358 core::rect<s32>(pos_crack, dim_crack_cropped));
1359 // Scale crack image by copying
1360 img_crack_cropped->copyToScaling(img_crack_scaled);
1361 // Copy or overlay crack image
1364 overlay(baseimg, img_crack_scaled);
1368 /*img_crack_scaled->copyToWithAlpha(
1371 core::rect<s32>(v2s32(0,0), dim_base),
1372 video::SColor(255,255,255,255));*/
1373 blit_with_alpha(img_crack_scaled, baseimg,
1374 v2s32(0,0), v2s32(0,0), dim_base);
1378 if(img_crack_scaled)
1379 img_crack_scaled->drop();
1381 if(img_crack_cropped)
1382 img_crack_cropped->drop();
1388 [combine:WxH:X,Y=filename:X,Y=filename2
1389 Creates a bigger texture from an amount of smaller ones
1391 else if(part_of_name.substr(0,8) == "[combine")
1393 Strfnd sf(part_of_name);
1395 u32 w0 = stoi(sf.next("x"));
1396 u32 h0 = stoi(sf.next(":"));
1397 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1398 core::dimension2d<u32> dim(w0,h0);
1401 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1402 baseimg->fill(video::SColor(0,0,0,0));
1404 while(sf.atend() == false)
1406 u32 x = stoi(sf.next(","));
1407 u32 y = stoi(sf.next("="));
1408 std::string filename = sf.next(":");
1409 infostream<<"Adding \""<<filename
1410 <<"\" to combined ("<<x<<","<<y<<")"
1412 video::IImage *img = sourcecache->getOrLoad(filename, device);
1415 core::dimension2d<u32> dim = img->getDimension();
1416 infostream<<"Size "<<dim.Width
1417 <<"x"<<dim.Height<<std::endl;
1418 core::position2d<s32> pos_base(x, y);
1419 video::IImage *img2 =
1420 driver->createImage(video::ECF_A8R8G8B8, dim);
1423 /*img2->copyToWithAlpha(baseimg, pos_base,
1424 core::rect<s32>(v2s32(0,0), dim),
1425 video::SColor(255,255,255,255),
1427 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1432 infostream<<"img==NULL"<<std::endl;
1439 else if(part_of_name.substr(0,9) == "[brighten")
1443 errorstream<<"generate_image(): baseimg==NULL "
1444 <<"for part_of_name=\""<<part_of_name
1445 <<"\", cancelling."<<std::endl;
1453 Make image completely opaque.
1454 Used for the leaves texture when in old leaves mode, so
1455 that the transparent parts don't look completely black
1456 when simple alpha channel is used for rendering.
1458 else if(part_of_name.substr(0,8) == "[noalpha")
1462 errorstream<<"generate_image(): baseimg==NULL "
1463 <<"for part_of_name=\""<<part_of_name
1464 <<"\", cancelling."<<std::endl;
1468 core::dimension2d<u32> dim = baseimg->getDimension();
1470 // Set alpha to full
1471 for(u32 y=0; y<dim.Height; y++)
1472 for(u32 x=0; x<dim.Width; x++)
1474 video::SColor c = baseimg->getPixel(x,y);
1476 baseimg->setPixel(x,y,c);
1481 Convert one color to transparent.
1483 else if(part_of_name.substr(0,11) == "[makealpha:")
1487 errorstream<<"generate_image(): baseimg==NULL "
1488 <<"for part_of_name=\""<<part_of_name
1489 <<"\", cancelling."<<std::endl;
1493 Strfnd sf(part_of_name.substr(11));
1494 u32 r1 = stoi(sf.next(","));
1495 u32 g1 = stoi(sf.next(","));
1496 u32 b1 = stoi(sf.next(""));
1497 std::string filename = sf.next("");
1499 core::dimension2d<u32> dim = baseimg->getDimension();
1501 /*video::IImage *oldbaseimg = baseimg;
1502 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1503 oldbaseimg->copyTo(baseimg);
1504 oldbaseimg->drop();*/
1506 // Set alpha to full
1507 for(u32 y=0; y<dim.Height; y++)
1508 for(u32 x=0; x<dim.Width; x++)
1510 video::SColor c = baseimg->getPixel(x,y);
1512 u32 g = c.getGreen();
1513 u32 b = c.getBlue();
1514 if(!(r == r1 && g == g1 && b == b1))
1517 baseimg->setPixel(x,y,c);
1522 Rotates and/or flips the image.
1524 N can be a number (between 0 and 7) or a transform name.
1525 Rotations are counter-clockwise.
1527 1 R90 rotate by 90 degrees
1528 2 R180 rotate by 180 degrees
1529 3 R270 rotate by 270 degrees
1531 5 FXR90 flip X then rotate by 90 degrees
1533 7 FYR90 flip Y then rotate by 90 degrees
1535 Note: Transform names can be concatenated to produce
1536 their product (applies the first then the second).
1537 The resulting transform will be equivalent to one of the
1538 eight existing ones, though (see: dihedral group).
1540 else if(part_of_name.substr(0,10) == "[transform")
1544 errorstream<<"generate_image(): baseimg==NULL "
1545 <<"for part_of_name=\""<<part_of_name
1546 <<"\", cancelling."<<std::endl;
1550 u32 transform = parseImageTransform(part_of_name.substr(10));
1551 core::dimension2d<u32> dim = imageTransformDimension(
1552 transform, baseimg->getDimension());
1553 video::IImage *image = driver->createImage(
1554 baseimg->getColorFormat(), dim);
1556 imageTransform(transform, baseimg, image);
1561 [inventorycube{topimage{leftimage{rightimage
1562 In every subimage, replace ^ with &.
1563 Create an "inventory cube".
1564 NOTE: This should be used only on its own.
1565 Example (a grass block (not actually used in game):
1566 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1568 else if(part_of_name.substr(0,14) == "[inventorycube")
1572 errorstream<<"generate_image(): baseimg!=NULL "
1573 <<"for part_of_name=\""<<part_of_name
1574 <<"\", cancelling."<<std::endl;
1578 str_replace_char(part_of_name, '&', '^');
1579 Strfnd sf(part_of_name);
1581 std::string imagename_top = sf.next("{");
1582 std::string imagename_left = sf.next("{");
1583 std::string imagename_right = sf.next("{");
1585 // Generate images for the faces of the cube
1586 video::IImage *img_top = generate_image_from_scratch(
1587 imagename_top, device, sourcecache);
1588 video::IImage *img_left = generate_image_from_scratch(
1589 imagename_left, device, sourcecache);
1590 video::IImage *img_right = generate_image_from_scratch(
1591 imagename_right, device, sourcecache);
1592 assert(img_top && img_left && img_right);
1594 // Create textures from images
1595 video::ITexture *texture_top = driver->addTexture(
1596 (imagename_top + "__temp__").c_str(), img_top);
1597 video::ITexture *texture_left = driver->addTexture(
1598 (imagename_left + "__temp__").c_str(), img_left);
1599 video::ITexture *texture_right = driver->addTexture(
1600 (imagename_right + "__temp__").c_str(), img_right);
1601 assert(texture_top && texture_left && texture_right);
1609 Draw a cube mesh into a render target texture
1611 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1612 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1613 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1614 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1615 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1616 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1617 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1618 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1620 core::dimension2d<u32> dim(64,64);
1621 std::string rtt_texture_name = part_of_name + "_RTT";
1623 v3f camera_position(0, 1.0, -1.5);
1624 camera_position.rotateXZBy(45);
1625 v3f camera_lookat(0, 0, 0);
1626 core::CMatrix4<f32> camera_projection_matrix;
1627 // Set orthogonal projection
1628 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1629 1.65, 1.65, 0, 100);
1631 video::SColorf ambient_light(0.2,0.2,0.2);
1632 v3f light_position(10, 100, -50);
1633 video::SColorf light_color(0.5,0.5,0.5);
1634 f32 light_radius = 1000;
1636 video::ITexture *rtt = generateTextureFromMesh(
1637 cube, device, dim, rtt_texture_name,
1640 camera_projection_matrix,
1649 // Free textures of images
1650 driver->removeTexture(texture_top);
1651 driver->removeTexture(texture_left);
1652 driver->removeTexture(texture_right);
1656 baseimg = generate_image_from_scratch(
1657 imagename_top, device, sourcecache);
1661 // Create image of render target
1662 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1666 driver->removeTexture(rtt);
1668 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1672 image->copyTo(baseimg);
1677 [lowpart:percent:filename
1678 Adds the lower part of a texture
1680 else if(part_of_name.substr(0,9) == "[lowpart:")
1682 Strfnd sf(part_of_name);
1684 u32 percent = stoi(sf.next(":"));
1685 std::string filename = sf.next(":");
1686 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1689 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1690 video::IImage *img = sourcecache->getOrLoad(filename, device);
1693 core::dimension2d<u32> dim = img->getDimension();
1694 core::position2d<s32> pos_base(0, 0);
1695 video::IImage *img2 =
1696 driver->createImage(video::ECF_A8R8G8B8, dim);
1699 core::position2d<s32> clippos(0, 0);
1700 clippos.Y = dim.Height * (100-percent) / 100;
1701 core::dimension2d<u32> clipdim = dim;
1702 clipdim.Height = clipdim.Height * percent / 100 + 1;
1703 core::rect<s32> cliprect(clippos, clipdim);
1704 img2->copyToWithAlpha(baseimg, pos_base,
1705 core::rect<s32>(v2s32(0,0), dim),
1706 video::SColor(255,255,255,255),
1713 Crops a frame of a vertical animation.
1714 N = frame count, I = frame index
1716 else if(part_of_name.substr(0,15) == "[verticalframe:")
1718 Strfnd sf(part_of_name);
1720 u32 frame_count = stoi(sf.next(":"));
1721 u32 frame_index = stoi(sf.next(":"));
1723 if(baseimg == NULL){
1724 errorstream<<"generate_image(): baseimg!=NULL "
1725 <<"for part_of_name=\""<<part_of_name
1726 <<"\", cancelling."<<std::endl;
1730 v2u32 frame_size = baseimg->getDimension();
1731 frame_size.Y /= frame_count;
1733 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1736 errorstream<<"generate_image(): Could not create image "
1737 <<"for part_of_name=\""<<part_of_name
1738 <<"\", cancelling."<<std::endl;
1742 // Fill target image with transparency
1743 img->fill(video::SColor(0,0,0,0));
1745 core::dimension2d<u32> dim = frame_size;
1746 core::position2d<s32> pos_dst(0, 0);
1747 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1748 baseimg->copyToWithAlpha(img, pos_dst,
1749 core::rect<s32>(pos_src, dim),
1750 video::SColor(255,255,255,255),
1758 errorstream<<"generate_image(): Invalid "
1759 " modification: \""<<part_of_name<<"\""<<std::endl;
1766 void overlay(video::IImage *image, video::IImage *overlay)
1769 Copy overlay to image, taking alpha into account.
1770 Where image is transparent, don't copy from overlay.
1771 Images sizes must be identical.
1773 if(image == NULL || overlay == NULL)
1776 core::dimension2d<u32> dim = image->getDimension();
1777 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1778 assert(dim == dim_overlay);
1780 for(u32 y=0; y<dim.Height; y++)
1781 for(u32 x=0; x<dim.Width; x++)
1783 video::SColor c1 = image->getPixel(x,y);
1784 video::SColor c2 = overlay->getPixel(x,y);
1785 u32 a1 = c1.getAlpha();
1786 u32 a2 = c2.getAlpha();
1787 if(a1 == 255 && a2 != 0)
1789 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1790 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1791 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1793 image->setPixel(x,y,c1);
1798 Draw an image on top of an another one, using the alpha channel of the
1801 This exists because IImage::copyToWithAlpha() doesn't seem to always
1804 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1805 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1807 for(u32 y0=0; y0<size.Y; y0++)
1808 for(u32 x0=0; x0<size.X; x0++)
1810 s32 src_x = src_pos.X + x0;
1811 s32 src_y = src_pos.Y + y0;
1812 s32 dst_x = dst_pos.X + x0;
1813 s32 dst_y = dst_pos.Y + y0;
1814 video::SColor src_c = src->getPixel(src_x, src_y);
1815 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1816 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1817 dst->setPixel(dst_x, dst_y, dst_c);
1821 void brighten(video::IImage *image)
1826 core::dimension2d<u32> dim = image->getDimension();
1828 for(u32 y=0; y<dim.Height; y++)
1829 for(u32 x=0; x<dim.Width; x++)
1831 video::SColor c = image->getPixel(x,y);
1832 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1833 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1834 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1835 image->setPixel(x,y,c);
1839 u32 parseImageTransform(const std::string& s)
1841 int total_transform = 0;
1843 std::string transform_names[8];
1844 transform_names[0] = "i";
1845 transform_names[1] = "r90";
1846 transform_names[2] = "r180";
1847 transform_names[3] = "r270";
1848 transform_names[4] = "fx";
1849 transform_names[6] = "fy";
1851 std::size_t pos = 0;
1852 while(pos < s.size())
1855 for(int i = 0; i <= 7; ++i)
1857 const std::string &name_i = transform_names[i];
1859 if(s[pos] == ('0' + i))
1865 else if(!(name_i.empty()) &&
1866 lowercase(s.substr(pos, name_i.size())) == name_i)
1869 pos += name_i.size();
1876 // Multiply total_transform and transform in the group D4
1879 new_total = (transform + total_transform) % 4;
1881 new_total = (transform - total_transform + 8) % 4;
1882 if((transform >= 4) ^ (total_transform >= 4))
1885 total_transform = new_total;
1887 return total_transform;
1890 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1892 if(transform % 2 == 0)
1895 return core::dimension2d<u32>(dim.Height, dim.Width);
1898 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1900 if(src == NULL || dst == NULL)
1903 core::dimension2d<u32> srcdim = src->getDimension();
1904 core::dimension2d<u32> dstdim = dst->getDimension();
1906 assert(dstdim == imageTransformDimension(transform, srcdim));
1907 assert(transform >= 0 && transform <= 7);
1910 Compute the transformation from source coordinates (sx,sy)
1911 to destination coordinates (dx,dy).
1915 if(transform == 0) // identity
1916 sxn = 0, syn = 2; // sx = dx, sy = dy
1917 else if(transform == 1) // rotate by 90 degrees ccw
1918 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1919 else if(transform == 2) // rotate by 180 degrees
1920 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1921 else if(transform == 3) // rotate by 270 degrees ccw
1922 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1923 else if(transform == 4) // flip x
1924 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1925 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1926 sxn = 2, syn = 0; // sx = dy, sy = dx
1927 else if(transform == 6) // flip y
1928 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1929 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1930 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1932 for(u32 dy=0; dy<dstdim.Height; dy++)
1933 for(u32 dx=0; dx<dstdim.Width; dx++)
1935 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1936 u32 sx = entries[sxn];
1937 u32 sy = entries[syn];
1938 video::SColor c = src->getPixel(sx,sy);
1939 dst->setPixel(dx,dy,c);