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 static 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
483 iter->atlas_img->drop();
485 m_atlaspointer_cache.clear();
487 infostream << "~TextureSource() "<< textures_before << "/"
488 << driver->getTextureCount() << std::endl;
491 u32 TextureSource::getTextureId(const std::string &name)
493 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
497 See if texture already exists
499 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
500 std::map<std::string, u32>::iterator n;
501 n = m_name_to_id.find(name);
502 if(n != m_name_to_id.end())
511 if(get_current_thread_id() == m_main_thread)
513 return getTextureIdDirect(name);
517 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
519 // We're gonna ask the result to be put into here
520 ResultQueue<std::string, u32, u8, u8> result_queue;
522 // Throw a request in
523 m_get_texture_queue.add(name, 0, 0, &result_queue);
525 infostream<<"Waiting for texture from main thread, name=\""
526 <<name<<"\""<<std::endl;
530 // Wait result for a second
531 GetResult<std::string, u32, u8, u8>
532 result = result_queue.pop_front(1000);
534 // Check that at least something worked OK
535 assert(result.key == name);
539 catch(ItemNotFoundException &e)
541 infostream<<"Waiting for texture timed out."<<std::endl;
546 infostream<<"getTextureId(): Failed"<<std::endl;
551 // Overlay image on top of another image (used for cracks)
552 void overlay(video::IImage *image, video::IImage *overlay);
554 // Draw an image on top of an another one, using the alpha channel of the
556 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
557 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
560 void brighten(video::IImage *image);
561 // Parse a transform name
562 u32 parseImageTransform(const std::string& s);
563 // Apply transform to image dimension
564 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
565 // Apply transform to image data
566 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
569 Generate image based on a string like "stone.png" or "[crack0".
570 if baseimg is NULL, it is created. Otherwise stuff is made on it.
572 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
573 IrrlichtDevice *device, SourceImageCache *sourcecache);
576 Generates an image from a full string like
577 "stone.png^mineral_coal.png^[crack0".
579 This is used by buildMainAtlas().
581 video::IImage* generate_image_from_scratch(std::string name,
582 IrrlichtDevice *device, SourceImageCache *sourcecache);
585 This method generates all the textures
587 u32 TextureSource::getTextureIdDirect(const std::string &name)
589 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
591 // Empty name means texture 0
594 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
599 Calling only allowed from main thread
601 if(get_current_thread_id() != m_main_thread)
603 errorstream<<"TextureSource::getTextureIdDirect() "
604 "called not from main thread"<<std::endl;
609 See if texture already exists
612 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
614 std::map<std::string, u32>::iterator n;
615 n = m_name_to_id.find(name);
616 if(n != m_name_to_id.end())
618 /*infostream<<"getTextureIdDirect(): \""<<name
619 <<"\" found in cache"<<std::endl;*/
624 /*infostream<<"getTextureIdDirect(): \""<<name
625 <<"\" NOT found in cache. Creating it."<<std::endl;*/
631 char separator = '^';
634 This is set to the id of the base image.
635 If left 0, there is no base image and a completely new image
638 u32 base_image_id = 0;
640 // Find last meta separator in name
641 s32 last_separator_position = -1;
642 for(s32 i=name.size()-1; i>=0; i--)
644 if(name[i] == separator)
646 last_separator_position = i;
651 If separator was found, construct the base name and make the
652 base image using a recursive call
654 std::string base_image_name;
655 if(last_separator_position != -1)
657 // Construct base name
658 base_image_name = name.substr(0, last_separator_position);
659 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
660 " to get base image of \""<<name<<"\" = \""
661 <<base_image_name<<"\""<<std::endl;*/
662 base_image_id = getTextureIdDirect(base_image_name);
665 //infostream<<"base_image_id="<<base_image_id<<std::endl;
667 video::IVideoDriver* driver = m_device->getVideoDriver();
670 video::ITexture *t = NULL;
673 An image will be built from files and then converted into a texture.
675 video::IImage *baseimg = NULL;
677 // If a base image was found, copy it to baseimg
678 if(base_image_id != 0)
680 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
682 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
684 video::IImage *image = ap.atlas_img;
688 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
689 <<"cache: \""<<base_image_name<<"\""
694 core::dimension2d<u32> dim = ap.intsize;
696 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
698 core::position2d<s32> pos_to(0,0);
699 core::position2d<s32> pos_from = ap.intpos;
703 v2s32(0,0), // position in target
704 core::rect<s32>(pos_from, dim) // from
707 /*infostream<<"getTextureIdDirect(): Loaded \""
708 <<base_image_name<<"\" from image cache"
714 Parse out the last part of the name of the image and act
718 std::string last_part_of_name = name.substr(last_separator_position+1);
719 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
721 // Generate image according to part of name
722 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
724 errorstream<<"getTextureIdDirect(): "
725 "failed to generate \""<<last_part_of_name<<"\""
729 // If no resulting image, print a warning
732 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
733 " create texture \""<<name<<"\""<<std::endl;
738 // Create texture from resulting image
739 t = driver->addTexture(name.c_str(), baseimg);
743 Add texture to caches (add NULL textures too)
746 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
748 u32 id = m_atlaspointer_cache.size();
754 core::dimension2d<u32> baseimg_dim(0,0);
756 baseimg_dim = baseimg->getDimension();
757 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
758 m_atlaspointer_cache.push_back(nap);
759 m_name_to_id[name] = id;
761 /*infostream<<"getTextureIdDirect(): "
762 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
767 std::string TextureSource::getTextureName(u32 id)
769 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
771 if(id >= m_atlaspointer_cache.size())
773 errorstream<<"TextureSource::getTextureName(): id="<<id
774 <<" >= m_atlaspointer_cache.size()="
775 <<m_atlaspointer_cache.size()<<std::endl;
779 return m_atlaspointer_cache[id].name;
783 AtlasPointer TextureSource::getTexture(u32 id)
785 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
787 if(id >= m_atlaspointer_cache.size())
788 return AtlasPointer(0, NULL);
790 return m_atlaspointer_cache[id].a;
793 void TextureSource::updateAP(AtlasPointer &ap)
795 AtlasPointer ap2 = getTexture(ap.id);
799 void TextureSource::processQueue()
804 if(!m_get_texture_queue.empty())
806 GetRequest<std::string, u32, u8, u8>
807 request = m_get_texture_queue.pop();
809 /*infostream<<"TextureSource::processQueue(): "
810 <<"got texture request with "
811 <<"name=\""<<request.key<<"\""
814 GetResult<std::string, u32, u8, u8>
816 result.key = request.key;
817 result.callers = request.callers;
818 result.item = getTextureIdDirect(request.key);
820 request.dest->push_back(result);
824 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
826 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
828 assert(get_current_thread_id() == m_main_thread);
830 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
831 m_source_image_existence.set(name, true);
834 void TextureSource::rebuildImagesAndTextures()
836 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
838 /*// Oh well... just clear everything, they'll load sometime.
839 m_atlaspointer_cache.clear();
840 m_name_to_id.clear();*/
842 video::IVideoDriver* driver = m_device->getVideoDriver();
844 // Remove source images from textures to disable inheriting textures
845 // from existing textures
846 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
847 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
848 sap->atlas_img->drop();
849 sap->atlas_img = NULL;
853 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
854 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
856 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
857 // Create texture from resulting image
858 video::ITexture *t = NULL;
860 t = driver->addTexture(sap->name.c_str(), img);
861 video::ITexture *t_old = sap->a.atlas;
864 sap->a.pos = v2f(0,0);
865 sap->a.size = v2f(1,1);
867 sap->atlas_img = img;
868 sap->intpos = v2s32(0,0);
869 sap->intsize = img->getDimension();
872 driver->removeTexture(t_old);
876 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
878 assert(gamedef->tsrc() == this);
879 INodeDefManager *ndef = gamedef->ndef();
881 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
883 //return; // Disable (for testing)
885 video::IVideoDriver* driver = m_device->getVideoDriver();
888 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
890 // Create an image of the right size
891 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
892 core::dimension2d<u32> atlas_dim(2048,2048);
893 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
894 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
895 video::IImage *atlas_img =
896 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
898 if(atlas_img == NULL)
900 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
901 "image; not building texture atlas."<<std::endl;
906 Grab list of stuff to include in the texture atlas from the
907 main content features
910 std::set<std::string> sourcelist;
912 for(u16 j=0; j<MAX_CONTENT+1; j++)
914 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
916 const ContentFeatures &f = ndef->get(j);
917 for(u32 i=0; i<6; i++)
919 std::string name = f.tiledef[i].name;
920 sourcelist.insert(name);
924 infostream<<"Creating texture atlas out of textures: ";
925 for(std::set<std::string>::iterator
926 i = sourcelist.begin();
927 i != sourcelist.end(); ++i)
929 std::string name = *i;
930 infostream<<"\""<<name<<"\" ";
932 infostream<<std::endl;
934 // Padding to disallow texture bleeding
935 // (16 needed if mipmapping is used; otherwise less will work too)
937 s32 column_padding = 16;
938 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
941 First pass: generate almost everything
943 core::position2d<s32> pos_in_atlas(0,0);
945 pos_in_atlas.X = column_padding;
946 pos_in_atlas.Y = padding;
948 for(std::set<std::string>::iterator
949 i = sourcelist.begin();
950 i != sourcelist.end(); ++i)
952 std::string name = *i;
954 // Generate image by name
955 video::IImage *img2 = generate_image_from_scratch(name, m_device,
959 errorstream<<"TextureSource::buildMainAtlas(): "
960 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
964 core::dimension2d<u32> dim = img2->getDimension();
966 // Don't add to atlas if image is too large
967 core::dimension2d<u32> max_size_in_atlas(64,64);
968 if(dim.Width > max_size_in_atlas.Width
969 || dim.Height > max_size_in_atlas.Height)
971 infostream<<"TextureSource::buildMainAtlas(): Not adding "
972 <<"\""<<name<<"\" because image is large"<<std::endl;
976 // Wrap columns and stop making atlas if atlas is full
977 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
979 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
980 errorstream<<"TextureSource::buildMainAtlas(): "
981 <<"Atlas is full, not adding more textures."
985 pos_in_atlas.Y = padding;
986 pos_in_atlas.X += column_width + column_padding*2;
989 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
990 <<"\" to texture atlas"<<std::endl;*/
992 // Tile it a few times in the X direction
993 u16 xwise_tiling = column_width / dim.Width;
994 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
996 for(u32 j=0; j<xwise_tiling; j++)
998 // Copy the copy to the atlas
999 /*img2->copyToWithAlpha(atlas_img,
1000 pos_in_atlas + v2s32(j*dim.Width,0),
1001 core::rect<s32>(v2s32(0,0), dim),
1002 video::SColor(255,255,255,255),
1004 img2->copyTo(atlas_img,
1005 pos_in_atlas + v2s32(j*dim.Width,0),
1006 core::rect<s32>(v2s32(0,0), dim),
1010 // Copy the borders a few times to disallow texture bleeding
1011 for(u32 side=0; side<2; side++) // top and bottom
1012 for(s32 y0=0; y0<padding; y0++)
1013 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
1019 dst_y = y0 + pos_in_atlas.Y + dim.Height;
1020 src_y = pos_in_atlas.Y + dim.Height - 1;
1024 dst_y = -y0 + pos_in_atlas.Y-1;
1025 src_y = pos_in_atlas.Y;
1027 s32 x = x0 + pos_in_atlas.X;
1028 video::SColor c = atlas_img->getPixel(x, src_y);
1029 atlas_img->setPixel(x,dst_y,c);
1032 for(u32 side=0; side<2; side++) // left and right
1033 for(s32 x0=0; x0<column_padding; x0++)
1034 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
1040 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
1041 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1045 dst_x = -x0 + pos_in_atlas.X-1;
1046 src_x = pos_in_atlas.X;
1048 s32 y = y0 + pos_in_atlas.Y;
1049 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1051 video::SColor c = atlas_img->getPixel(src_x, src_y);
1052 atlas_img->setPixel(dst_x,dst_y,c);
1058 Add texture to caches
1061 bool reuse_old_id = false;
1062 u32 id = m_atlaspointer_cache.size();
1063 // Check old id without fetching a texture
1064 std::map<std::string, u32>::iterator n;
1065 n = m_name_to_id.find(name);
1066 // If it exists, we will replace the old definition
1067 if(n != m_name_to_id.end()){
1069 reuse_old_id = true;
1070 /*infostream<<"TextureSource::buildMainAtlas(): "
1071 <<"Replacing old AtlasPointer"<<std::endl;*/
1074 // Create AtlasPointer
1075 AtlasPointer ap(id);
1076 ap.atlas = NULL; // Set on the second pass
1077 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1078 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1079 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1080 (float)dim.Width/(float)atlas_dim.Height);
1081 ap.tiled = xwise_tiling;
1083 // Create SourceAtlasPointer and add to containers
1084 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1086 m_atlaspointer_cache[id] = nap;
1088 m_atlaspointer_cache.push_back(nap);
1089 m_name_to_id[name] = id;
1091 // Increment position
1092 pos_in_atlas.Y += dim.Height + padding * 2;
1098 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1102 Second pass: set texture pointer in generated AtlasPointers
1104 for(std::set<std::string>::iterator
1105 i = sourcelist.begin();
1106 i != sourcelist.end(); ++i)
1108 std::string name = *i;
1109 if(m_name_to_id.find(name) == m_name_to_id.end())
1111 u32 id = m_name_to_id[name];
1112 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1113 m_atlaspointer_cache[id].a.atlas = t;
1117 Write image to file so that it can be inspected
1119 /*std::string atlaspath = porting::path_user
1120 + DIR_DELIM + "generated_texture_atlas.png";
1121 infostream<<"Removing and writing texture atlas for inspection to "
1122 <<atlaspath<<std::endl;
1123 fs::RecursiveDelete(atlaspath);
1124 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1127 video::IImage* generate_image_from_scratch(std::string name,
1128 IrrlichtDevice *device, SourceImageCache *sourcecache)
1130 /*infostream<<"generate_image_from_scratch(): "
1131 "\""<<name<<"\""<<std::endl;*/
1133 video::IVideoDriver* driver = device->getVideoDriver();
1140 video::IImage *baseimg = NULL;
1142 char separator = '^';
1144 // Find last meta separator in name
1145 s32 last_separator_position = name.find_last_of(separator);
1146 //if(last_separator_position == std::npos)
1147 // last_separator_position = -1;
1149 /*infostream<<"generate_image_from_scratch(): "
1150 <<"last_separator_position="<<last_separator_position
1154 If separator was found, construct the base name and make the
1155 base image using a recursive call
1157 std::string base_image_name;
1158 if(last_separator_position != -1)
1160 // Construct base name
1161 base_image_name = name.substr(0, last_separator_position);
1162 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1163 " to get base image of \""<<name<<"\" = \""
1164 <<base_image_name<<"\""<<std::endl;*/
1165 baseimg = generate_image_from_scratch(base_image_name, device,
1170 Parse out the last part of the name of the image and act
1174 std::string last_part_of_name = name.substr(last_separator_position+1);
1175 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1177 // Generate image according to part of name
1178 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1180 errorstream<<"generate_image_from_scratch(): "
1181 "failed to generate \""<<last_part_of_name<<"\""
1189 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1190 IrrlichtDevice *device, SourceImageCache *sourcecache)
1192 video::IVideoDriver* driver = device->getVideoDriver();
1195 // Stuff starting with [ are special commands
1196 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1198 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1202 if(part_of_name != ""){
1203 errorstream<<"generate_image(): Could not load image \""
1204 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1205 errorstream<<"generate_image(): Creating a dummy"
1206 <<" image for \""<<part_of_name<<"\""<<std::endl;
1209 // Just create a dummy image
1210 //core::dimension2d<u32> dim(2,2);
1211 core::dimension2d<u32> dim(1,1);
1212 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1214 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1215 image->setPixel(1,0, video::SColor(255,0,255,0));
1216 image->setPixel(0,1, video::SColor(255,0,0,255));
1217 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1218 image->setPixel(0,0, video::SColor(255,myrand()%256,
1219 myrand()%256,myrand()%256));
1220 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1221 myrand()%256,myrand()%256));
1222 image->setPixel(0,1, video::SColor(255,myrand()%256,
1223 myrand()%256,myrand()%256));
1224 image->setPixel(1,1, video::SColor(255,myrand()%256,
1225 myrand()%256,myrand()%256));*/
1228 // If base image is NULL, load as base.
1231 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1233 Copy it this way to get an alpha channel.
1234 Otherwise images with alpha cannot be blitted on
1235 images that don't have alpha in the original file.
1237 core::dimension2d<u32> dim = image->getDimension();
1238 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1239 image->copyTo(baseimg);
1241 // Else blit on base.
1244 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1245 // Size of the copied area
1246 core::dimension2d<u32> dim = image->getDimension();
1247 //core::dimension2d<u32> dim(16,16);
1248 // Position to copy the blitted to in the base image
1249 core::position2d<s32> pos_to(0,0);
1250 // Position to copy the blitted from in the blitted image
1251 core::position2d<s32> pos_from(0,0);
1253 /*image->copyToWithAlpha(baseimg, pos_to,
1254 core::rect<s32>(pos_from, dim),
1255 video::SColor(255,255,255,255),
1257 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1264 // A special texture modification
1266 /*infostream<<"generate_image(): generating special "
1267 <<"modification \""<<part_of_name<<"\""
1271 This is the simplest of all; it just adds stuff to the
1272 name so that a separate texture is created.
1274 It is used to make textures for stuff that doesn't want
1275 to implement getting the texture from a bigger texture
1278 if(part_of_name == "[forcesingle")
1280 // If base image is NULL, create a random color
1283 core::dimension2d<u32> dim(1,1);
1284 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1286 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1287 myrand()%256,myrand()%256));
1292 Adds a cracking texture
1294 else if(part_of_name.substr(0,6) == "[crack")
1298 errorstream<<"generate_image(): baseimg==NULL "
1299 <<"for part_of_name=\""<<part_of_name
1300 <<"\", cancelling."<<std::endl;
1304 // Crack image number and overlay option
1305 s32 progression = 0;
1306 bool use_overlay = false;
1307 if(part_of_name.substr(6,1) == "o")
1309 progression = stoi(part_of_name.substr(7));
1314 progression = stoi(part_of_name.substr(6));
1315 use_overlay = false;
1318 // Size of the base image
1319 core::dimension2d<u32> dim_base = baseimg->getDimension();
1324 It is an image with a number of cracking stages
1327 video::IImage *img_crack = sourcecache->getOrLoad(
1328 "crack_anylength.png", device);
1330 if(img_crack && progression >= 0)
1332 // Dimension of original image
1333 core::dimension2d<u32> dim_crack
1334 = img_crack->getDimension();
1335 // Count of crack stages
1336 s32 crack_count = dim_crack.Height / dim_crack.Width;
1337 // Limit progression
1338 if(progression > crack_count-1)
1339 progression = crack_count-1;
1340 // Dimension of a single crack stage
1341 core::dimension2d<u32> dim_crack_cropped(
1345 // Create cropped and scaled crack images
1346 video::IImage *img_crack_cropped = driver->createImage(
1347 video::ECF_A8R8G8B8, dim_crack_cropped);
1348 video::IImage *img_crack_scaled = driver->createImage(
1349 video::ECF_A8R8G8B8, dim_base);
1351 if(img_crack_cropped && img_crack_scaled)
1354 v2s32 pos_crack(0, progression*dim_crack.Width);
1355 img_crack->copyTo(img_crack_cropped,
1357 core::rect<s32>(pos_crack, dim_crack_cropped));
1358 // Scale crack image by copying
1359 img_crack_cropped->copyToScaling(img_crack_scaled);
1360 // Copy or overlay crack image
1363 overlay(baseimg, img_crack_scaled);
1367 /*img_crack_scaled->copyToWithAlpha(
1370 core::rect<s32>(v2s32(0,0), dim_base),
1371 video::SColor(255,255,255,255));*/
1372 blit_with_alpha(img_crack_scaled, baseimg,
1373 v2s32(0,0), v2s32(0,0), dim_base);
1377 if(img_crack_scaled)
1378 img_crack_scaled->drop();
1380 if(img_crack_cropped)
1381 img_crack_cropped->drop();
1387 [combine:WxH:X,Y=filename:X,Y=filename2
1388 Creates a bigger texture from an amount of smaller ones
1390 else if(part_of_name.substr(0,8) == "[combine")
1392 Strfnd sf(part_of_name);
1394 u32 w0 = stoi(sf.next("x"));
1395 u32 h0 = stoi(sf.next(":"));
1396 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1397 core::dimension2d<u32> dim(w0,h0);
1400 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1401 baseimg->fill(video::SColor(0,0,0,0));
1403 while(sf.atend() == false)
1405 u32 x = stoi(sf.next(","));
1406 u32 y = stoi(sf.next("="));
1407 std::string filename = sf.next(":");
1408 infostream<<"Adding \""<<filename
1409 <<"\" to combined ("<<x<<","<<y<<")"
1411 video::IImage *img = sourcecache->getOrLoad(filename, device);
1414 core::dimension2d<u32> dim = img->getDimension();
1415 infostream<<"Size "<<dim.Width
1416 <<"x"<<dim.Height<<std::endl;
1417 core::position2d<s32> pos_base(x, y);
1418 video::IImage *img2 =
1419 driver->createImage(video::ECF_A8R8G8B8, dim);
1422 /*img2->copyToWithAlpha(baseimg, pos_base,
1423 core::rect<s32>(v2s32(0,0), dim),
1424 video::SColor(255,255,255,255),
1426 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1431 infostream<<"img==NULL"<<std::endl;
1438 else if(part_of_name.substr(0,9) == "[brighten")
1442 errorstream<<"generate_image(): baseimg==NULL "
1443 <<"for part_of_name=\""<<part_of_name
1444 <<"\", cancelling."<<std::endl;
1452 Make image completely opaque.
1453 Used for the leaves texture when in old leaves mode, so
1454 that the transparent parts don't look completely black
1455 when simple alpha channel is used for rendering.
1457 else if(part_of_name.substr(0,8) == "[noalpha")
1461 errorstream<<"generate_image(): baseimg==NULL "
1462 <<"for part_of_name=\""<<part_of_name
1463 <<"\", cancelling."<<std::endl;
1467 core::dimension2d<u32> dim = baseimg->getDimension();
1469 // Set alpha to full
1470 for(u32 y=0; y<dim.Height; y++)
1471 for(u32 x=0; x<dim.Width; x++)
1473 video::SColor c = baseimg->getPixel(x,y);
1475 baseimg->setPixel(x,y,c);
1480 Convert one color to transparent.
1482 else if(part_of_name.substr(0,11) == "[makealpha:")
1486 errorstream<<"generate_image(): baseimg==NULL "
1487 <<"for part_of_name=\""<<part_of_name
1488 <<"\", cancelling."<<std::endl;
1492 Strfnd sf(part_of_name.substr(11));
1493 u32 r1 = stoi(sf.next(","));
1494 u32 g1 = stoi(sf.next(","));
1495 u32 b1 = stoi(sf.next(""));
1496 std::string filename = sf.next("");
1498 core::dimension2d<u32> dim = baseimg->getDimension();
1500 /*video::IImage *oldbaseimg = baseimg;
1501 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1502 oldbaseimg->copyTo(baseimg);
1503 oldbaseimg->drop();*/
1505 // Set alpha to full
1506 for(u32 y=0; y<dim.Height; y++)
1507 for(u32 x=0; x<dim.Width; x++)
1509 video::SColor c = baseimg->getPixel(x,y);
1511 u32 g = c.getGreen();
1512 u32 b = c.getBlue();
1513 if(!(r == r1 && g == g1 && b == b1))
1516 baseimg->setPixel(x,y,c);
1521 Rotates and/or flips the image.
1523 N can be a number (between 0 and 7) or a transform name.
1524 Rotations are counter-clockwise.
1526 1 R90 rotate by 90 degrees
1527 2 R180 rotate by 180 degrees
1528 3 R270 rotate by 270 degrees
1530 5 FXR90 flip X then rotate by 90 degrees
1532 7 FYR90 flip Y then rotate by 90 degrees
1534 Note: Transform names can be concatenated to produce
1535 their product (applies the first then the second).
1536 The resulting transform will be equivalent to one of the
1537 eight existing ones, though (see: dihedral group).
1539 else if(part_of_name.substr(0,10) == "[transform")
1543 errorstream<<"generate_image(): baseimg==NULL "
1544 <<"for part_of_name=\""<<part_of_name
1545 <<"\", cancelling."<<std::endl;
1549 u32 transform = parseImageTransform(part_of_name.substr(10));
1550 core::dimension2d<u32> dim = imageTransformDimension(
1551 transform, baseimg->getDimension());
1552 video::IImage *image = driver->createImage(
1553 baseimg->getColorFormat(), dim);
1555 imageTransform(transform, baseimg, image);
1560 [inventorycube{topimage{leftimage{rightimage
1561 In every subimage, replace ^ with &.
1562 Create an "inventory cube".
1563 NOTE: This should be used only on its own.
1564 Example (a grass block (not actually used in game):
1565 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1567 else if(part_of_name.substr(0,14) == "[inventorycube")
1571 errorstream<<"generate_image(): baseimg!=NULL "
1572 <<"for part_of_name=\""<<part_of_name
1573 <<"\", cancelling."<<std::endl;
1577 str_replace_char(part_of_name, '&', '^');
1578 Strfnd sf(part_of_name);
1580 std::string imagename_top = sf.next("{");
1581 std::string imagename_left = sf.next("{");
1582 std::string imagename_right = sf.next("{");
1584 // Generate images for the faces of the cube
1585 video::IImage *img_top = generate_image_from_scratch(
1586 imagename_top, device, sourcecache);
1587 video::IImage *img_left = generate_image_from_scratch(
1588 imagename_left, device, sourcecache);
1589 video::IImage *img_right = generate_image_from_scratch(
1590 imagename_right, device, sourcecache);
1591 assert(img_top && img_left && img_right);
1593 // Create textures from images
1594 video::ITexture *texture_top = driver->addTexture(
1595 (imagename_top + "__temp__").c_str(), img_top);
1596 video::ITexture *texture_left = driver->addTexture(
1597 (imagename_left + "__temp__").c_str(), img_left);
1598 video::ITexture *texture_right = driver->addTexture(
1599 (imagename_right + "__temp__").c_str(), img_right);
1600 assert(texture_top && texture_left && texture_right);
1608 Draw a cube mesh into a render target texture
1610 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1611 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1612 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1613 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1614 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1615 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1616 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1617 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1619 core::dimension2d<u32> dim(64,64);
1620 std::string rtt_texture_name = part_of_name + "_RTT";
1622 v3f camera_position(0, 1.0, -1.5);
1623 camera_position.rotateXZBy(45);
1624 v3f camera_lookat(0, 0, 0);
1625 core::CMatrix4<f32> camera_projection_matrix;
1626 // Set orthogonal projection
1627 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1628 1.65, 1.65, 0, 100);
1630 video::SColorf ambient_light(0.2,0.2,0.2);
1631 v3f light_position(10, 100, -50);
1632 video::SColorf light_color(0.5,0.5,0.5);
1633 f32 light_radius = 1000;
1635 video::ITexture *rtt = generateTextureFromMesh(
1636 cube, device, dim, rtt_texture_name,
1639 camera_projection_matrix,
1648 // Free textures of images
1649 driver->removeTexture(texture_top);
1650 driver->removeTexture(texture_left);
1651 driver->removeTexture(texture_right);
1655 baseimg = generate_image_from_scratch(
1656 imagename_top, device, sourcecache);
1660 // Create image of render target
1661 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1664 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1668 image->copyTo(baseimg);
1673 [lowpart:percent:filename
1674 Adds the lower part of a texture
1676 else if(part_of_name.substr(0,9) == "[lowpart:")
1678 Strfnd sf(part_of_name);
1680 u32 percent = stoi(sf.next(":"));
1681 std::string filename = sf.next(":");
1682 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1685 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1686 video::IImage *img = sourcecache->getOrLoad(filename, device);
1689 core::dimension2d<u32> dim = img->getDimension();
1690 core::position2d<s32> pos_base(0, 0);
1691 video::IImage *img2 =
1692 driver->createImage(video::ECF_A8R8G8B8, dim);
1695 core::position2d<s32> clippos(0, 0);
1696 clippos.Y = dim.Height * (100-percent) / 100;
1697 core::dimension2d<u32> clipdim = dim;
1698 clipdim.Height = clipdim.Height * percent / 100 + 1;
1699 core::rect<s32> cliprect(clippos, clipdim);
1700 img2->copyToWithAlpha(baseimg, pos_base,
1701 core::rect<s32>(v2s32(0,0), dim),
1702 video::SColor(255,255,255,255),
1709 Crops a frame of a vertical animation.
1710 N = frame count, I = frame index
1712 else if(part_of_name.substr(0,15) == "[verticalframe:")
1714 Strfnd sf(part_of_name);
1716 u32 frame_count = stoi(sf.next(":"));
1717 u32 frame_index = stoi(sf.next(":"));
1719 if(baseimg == NULL){
1720 errorstream<<"generate_image(): baseimg!=NULL "
1721 <<"for part_of_name=\""<<part_of_name
1722 <<"\", cancelling."<<std::endl;
1726 v2u32 frame_size = baseimg->getDimension();
1727 frame_size.Y /= frame_count;
1729 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1732 errorstream<<"generate_image(): Could not create image "
1733 <<"for part_of_name=\""<<part_of_name
1734 <<"\", cancelling."<<std::endl;
1738 // Fill target image with transparency
1739 img->fill(video::SColor(0,0,0,0));
1741 core::dimension2d<u32> dim = frame_size;
1742 core::position2d<s32> pos_dst(0, 0);
1743 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1744 baseimg->copyToWithAlpha(img, pos_dst,
1745 core::rect<s32>(pos_src, dim),
1746 video::SColor(255,255,255,255),
1754 errorstream<<"generate_image(): Invalid "
1755 " modification: \""<<part_of_name<<"\""<<std::endl;
1762 void overlay(video::IImage *image, video::IImage *overlay)
1765 Copy overlay to image, taking alpha into account.
1766 Where image is transparent, don't copy from overlay.
1767 Images sizes must be identical.
1769 if(image == NULL || overlay == NULL)
1772 core::dimension2d<u32> dim = image->getDimension();
1773 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1774 assert(dim == dim_overlay);
1776 for(u32 y=0; y<dim.Height; y++)
1777 for(u32 x=0; x<dim.Width; x++)
1779 video::SColor c1 = image->getPixel(x,y);
1780 video::SColor c2 = overlay->getPixel(x,y);
1781 u32 a1 = c1.getAlpha();
1782 u32 a2 = c2.getAlpha();
1783 if(a1 == 255 && a2 != 0)
1785 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1786 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1787 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1789 image->setPixel(x,y,c1);
1794 Draw an image on top of an another one, using the alpha channel of the
1797 This exists because IImage::copyToWithAlpha() doesn't seem to always
1800 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1801 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1803 for(u32 y0=0; y0<size.Y; y0++)
1804 for(u32 x0=0; x0<size.X; x0++)
1806 s32 src_x = src_pos.X + x0;
1807 s32 src_y = src_pos.Y + y0;
1808 s32 dst_x = dst_pos.X + x0;
1809 s32 dst_y = dst_pos.Y + y0;
1810 video::SColor src_c = src->getPixel(src_x, src_y);
1811 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1812 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1813 dst->setPixel(dst_x, dst_y, dst_c);
1817 void brighten(video::IImage *image)
1822 core::dimension2d<u32> dim = image->getDimension();
1824 for(u32 y=0; y<dim.Height; y++)
1825 for(u32 x=0; x<dim.Width; x++)
1827 video::SColor c = image->getPixel(x,y);
1828 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1829 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1830 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1831 image->setPixel(x,y,c);
1835 u32 parseImageTransform(const std::string& s)
1837 int total_transform = 0;
1839 std::string transform_names[8];
1840 transform_names[0] = "i";
1841 transform_names[1] = "r90";
1842 transform_names[2] = "r180";
1843 transform_names[3] = "r270";
1844 transform_names[4] = "fx";
1845 transform_names[6] = "fy";
1847 std::size_t pos = 0;
1848 while(pos < s.size())
1851 for(int i = 0; i <= 7; ++i)
1853 const std::string &name_i = transform_names[i];
1855 if(s[pos] == ('0' + i))
1861 else if(!(name_i.empty()) &&
1862 lowercase(s.substr(pos, name_i.size())) == name_i)
1865 pos += name_i.size();
1872 // Multiply total_transform and transform in the group D4
1875 new_total = (transform + total_transform) % 4;
1877 new_total = (transform - total_transform + 8) % 4;
1878 if((transform >= 4) ^ (total_transform >= 4))
1881 total_transform = new_total;
1883 return total_transform;
1886 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1888 if(transform % 2 == 0)
1891 return core::dimension2d<u32>(dim.Height, dim.Width);
1894 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1896 if(src == NULL || dst == NULL)
1899 core::dimension2d<u32> srcdim = src->getDimension();
1900 core::dimension2d<u32> dstdim = dst->getDimension();
1902 assert(dstdim == imageTransformDimension(transform, srcdim));
1903 assert(transform >= 0 && transform <= 7);
1906 Compute the transformation from source coordinates (sx,sy)
1907 to destination coordinates (dx,dy).
1911 if(transform == 0) // identity
1912 sxn = 0, syn = 2; // sx = dx, sy = dy
1913 else if(transform == 1) // rotate by 90 degrees ccw
1914 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1915 else if(transform == 2) // rotate by 180 degrees
1916 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1917 else if(transform == 3) // rotate by 270 degrees ccw
1918 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1919 else if(transform == 4) // flip x
1920 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1921 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1922 sxn = 2, syn = 0; // sx = dy, sy = dx
1923 else if(transform == 6) // flip y
1924 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1925 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1926 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1928 for(u32 dy=0; dy<dstdim.Height; dy++)
1929 for(u32 dx=0; dx<dstdim.Width; dx++)
1931 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1932 u32 sx = entries[sxn];
1933 u32 sy = entries[syn];
1934 video::SColor c = src->getPixel(sx,sy);
1935 dst->setPixel(dx,dy,c);