3 Copyright (C) 2010-2011 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
27 #include <ICameraSceneNode.h>
29 #include "mapnode.h" // For texture atlas making
30 #include "nodedef.h" // For texture atlas making
32 #include "utility_string.h"
35 A cache from texture name to texture path
37 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
40 Replaces the filename extension.
42 std::string image = "a/image.png"
43 replace_ext(image, "jpg")
44 -> image = "a/image.jpg"
45 Returns true on success.
47 static bool replace_ext(std::string &path, const char *ext)
51 // Find place of last dot, fail if \ or / found.
53 for(s32 i=path.size()-1; i>=0; i--)
61 if(path[i] == '\\' || path[i] == '/')
64 // If not found, return an empty string
67 // Else make the new path
68 path = path.substr(0, last_dot_i+1) + ext;
73 Find out the full path of an image by trying different filename
78 static std::string getImagePath(std::string path)
80 // A NULL-ended list of possible image extensions
81 const char *extensions[] = {
82 "png", "jpg", "bmp", "tga",
83 "pcx", "ppm", "psd", "wal", "rgb",
86 // If there is no extension, add one
87 if(removeStringEnd(path, extensions) == "")
89 // Check paths until something is found to exist
90 const char **ext = extensions;
92 bool r = replace_ext(path, *ext);
95 if(fs::PathExists(path))
98 while((++ext) != NULL);
104 Gets the path to a texture by first checking if the texture exists
105 in texture_path and if not, using the data path.
107 Checks all supported extensions by replacing the original extension.
109 If not found, returns "".
111 Utilizes a thread-safe cache.
113 std::string getTexturePath(const std::string &filename)
115 std::string fullpath = "";
119 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
124 Check from texture_path
126 std::string texture_path = g_settings->get("texture_path");
127 if(texture_path != "")
129 std::string testpath = texture_path + DIR_DELIM + filename;
130 // Check all filename extensions. Returns "" if not found.
131 fullpath = getImagePath(testpath);
135 Check from $user/textures/all
139 std::string texture_path = porting::path_user + DIR_DELIM
140 + "textures" + DIR_DELIM + "all";
141 std::string testpath = texture_path + DIR_DELIM + filename;
142 // Check all filename extensions. Returns "" if not found.
143 fullpath = getImagePath(testpath);
147 Check from default data directory
151 std::string base_path = porting::path_share + DIR_DELIM + "textures"
152 + DIR_DELIM + "base" + DIR_DELIM + "pack";
153 std::string testpath = base_path + DIR_DELIM + filename;
154 // Check all filename extensions. Returns "" if not found.
155 fullpath = getImagePath(testpath);
158 // Add to cache (also an empty result is cached)
159 g_texturename_to_path_cache.set(filename, fullpath);
166 An internal variant of AtlasPointer with more data.
167 (well, more like a wrapper)
170 struct SourceAtlasPointer
174 video::IImage *atlas_img; // The source image of the atlas
175 // Integer variants of position and size
180 const std::string &name_,
181 AtlasPointer a_=AtlasPointer(0, NULL),
182 video::IImage *atlas_img_=NULL,
183 v2s32 intpos_=v2s32(0,0),
184 v2u32 intsize_=v2u32(0,0)
188 atlas_img(atlas_img_),
196 SourceImageCache: A cache used for storing source images.
199 class SourceImageCache
202 void insert(const std::string &name, video::IImage *img,
203 bool prefer_local, video::IVideoDriver *driver)
207 core::map<std::string, video::IImage*>::Node *n;
208 n = m_images.find(name);
210 video::IImage *oldimg = n->getValue();
214 // Try to use local texture instead if asked to
216 std::string path = getTexturePath(name.c_str());
218 video::IImage *img2 = driver->createImageFromFile(path.c_str());
220 m_images[name] = img2;
226 m_images[name] = img;
228 video::IImage* get(const std::string &name)
230 core::map<std::string, video::IImage*>::Node *n;
231 n = m_images.find(name);
233 return n->getValue();
236 // Primarily fetches from cache, secondarily tries to read from filesystem
237 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
239 core::map<std::string, video::IImage*>::Node *n;
240 n = m_images.find(name);
242 n->getValue()->grab(); // Grab for caller
243 return n->getValue();
245 video::IVideoDriver* driver = device->getVideoDriver();
246 std::string path = getTexturePath(name.c_str());
248 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
249 <<name<<"\""<<std::endl;
252 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
254 video::IImage *img = driver->createImageFromFile(path.c_str());
255 // Even if could not be loaded, put as NULL
256 //m_images[name] = img;
258 m_images[name] = img;
259 img->grab(); // Grab for caller
264 core::map<std::string, video::IImage*> m_images;
271 class TextureSource : public IWritableTextureSource
274 TextureSource(IrrlichtDevice *device);
279 Now, assume a texture with the id 1 exists, and has the name
280 "stone.png^mineral1".
281 Then a random thread calls getTextureId for a texture called
282 "stone.png^mineral1^crack0".
283 ...Now, WTF should happen? Well:
284 - getTextureId strips off stuff recursively from the end until
285 the remaining part is found, or nothing is left when
286 something is stripped out
288 But it is slow to search for textures by names and modify them
290 - ContentFeatures is made to contain ids for the basic plain
292 - Crack textures can be slow by themselves, but the framework
296 - Assume a texture with the id 1 exists, and has the name
297 "stone.png^mineral1" and is specified as a part of some atlas.
298 - Now getNodeTile() stumbles upon a node which uses
299 texture id 1, and determines that MATERIAL_FLAG_CRACK
300 must be applied to the tile
301 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
302 has received the current crack level 0 from the client. It
303 finds out the name of the texture with getTextureName(1),
304 appends "^crack0" to it and gets a new texture id with
305 getTextureId("stone.png^mineral1^crack0").
310 Gets a texture id from cache or
311 - if main thread, from getTextureIdDirect
312 - if other thread, adds to request queue and waits for main thread
314 u32 getTextureId(const std::string &name);
320 "stone.png^mineral_coal.png"
321 "stone.png^mineral_coal.png^crack1"
323 - If texture specified by name is found from cache, return the
325 - Otherwise generate the texture, add to cache and return id.
326 Recursion is used to find out the largest found part of the
327 texture and continue based on it.
329 The id 0 points to a NULL texture. It is returned in case of error.
331 u32 getTextureIdDirect(const std::string &name);
333 // Finds out the name of a cached texture.
334 std::string getTextureName(u32 id);
337 If texture specified by the name pointed by the id doesn't
338 exist, create it, then return the cached texture.
340 Can be called from any thread. If called from some other thread
341 and not found in cache, the call is queued to the main thread
344 AtlasPointer getTexture(u32 id);
346 AtlasPointer getTexture(const std::string &name)
348 return getTexture(getTextureId(name));
351 // Gets a separate texture
352 video::ITexture* getTextureRaw(const std::string &name)
354 AtlasPointer ap = getTexture(name + "^[forcesingle");
358 // Gets a separate texture atlas pointer
359 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
361 return getTexture(getTextureName(ap.id) + "^[forcesingle");
364 // Returns a pointer to the irrlicht device
365 virtual IrrlichtDevice* getDevice()
370 // Update new texture pointer and texture coordinates to an
371 // AtlasPointer based on it's texture id
372 void updateAP(AtlasPointer &ap);
374 // Processes queued texture requests from other threads.
375 // Shall be called from the main thread.
378 // Insert an image into the cache without touching the filesystem.
379 // Shall be called from the main thread.
380 void insertSourceImage(const std::string &name, video::IImage *img);
382 // Rebuild images and textures from the current set of source images
383 // Shall be called from the main thread.
384 void rebuildImagesAndTextures();
386 // Build the main texture atlas which contains most of the
388 void buildMainAtlas(class IGameDef *gamedef);
392 // The id of the thread that is allowed to use irrlicht directly
393 threadid_t m_main_thread;
394 // The irrlicht device
395 IrrlichtDevice *m_device;
397 // Cache of source images
398 // This should be only accessed from the main thread
399 SourceImageCache m_sourcecache;
401 // A texture id is index in this array.
402 // The first position contains a NULL texture.
403 core::array<SourceAtlasPointer> m_atlaspointer_cache;
404 // Maps a texture name to an index in the former.
405 core::map<std::string, u32> m_name_to_id;
406 // The two former containers are behind this mutex
407 JMutex m_atlaspointer_cache_mutex;
409 // Main texture atlas. This is filled at startup and is then not touched.
410 video::IImage *m_main_atlas_image;
411 video::ITexture *m_main_atlas_texture;
413 // Queued texture fetches (to be processed by the main thread)
414 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
417 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
419 return new TextureSource(device);
422 TextureSource::TextureSource(IrrlichtDevice *device):
424 m_main_atlas_image(NULL),
425 m_main_atlas_texture(NULL)
429 m_atlaspointer_cache_mutex.Init();
431 m_main_thread = get_current_thread_id();
433 // Add a NULL AtlasPointer as the first index, named ""
434 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
435 m_name_to_id[""] = 0;
438 TextureSource::~TextureSource()
442 u32 TextureSource::getTextureId(const std::string &name)
444 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
448 See if texture already exists
450 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
451 core::map<std::string, u32>::Node *n;
452 n = m_name_to_id.find(name);
455 return n->getValue();
462 if(get_current_thread_id() == m_main_thread)
464 return getTextureIdDirect(name);
468 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
470 // We're gonna ask the result to be put into here
471 ResultQueue<std::string, u32, u8, u8> result_queue;
473 // Throw a request in
474 m_get_texture_queue.add(name, 0, 0, &result_queue);
476 infostream<<"Waiting for texture from main thread, name=\""
477 <<name<<"\""<<std::endl;
481 // Wait result for a second
482 GetResult<std::string, u32, u8, u8>
483 result = result_queue.pop_front(1000);
485 // Check that at least something worked OK
486 assert(result.key == name);
490 catch(ItemNotFoundException &e)
492 infostream<<"Waiting for texture timed out."<<std::endl;
497 infostream<<"getTextureId(): Failed"<<std::endl;
502 // Overlay image on top of another image (used for cracks)
503 void overlay(video::IImage *image, video::IImage *overlay);
505 // Draw an image on top of an another one, using the alpha channel of the
507 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
508 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
511 void brighten(video::IImage *image);
512 // Parse a transform name
513 u32 parseImageTransform(const std::string& s);
514 // Apply transform to image dimension
515 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
516 // Apply transform to image data
517 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
520 Generate image based on a string like "stone.png" or "[crack0".
521 if baseimg is NULL, it is created. Otherwise stuff is made on it.
523 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
524 IrrlichtDevice *device, SourceImageCache *sourcecache);
527 Generates an image from a full string like
528 "stone.png^mineral_coal.png^[crack0".
530 This is used by buildMainAtlas().
532 video::IImage* generate_image_from_scratch(std::string name,
533 IrrlichtDevice *device, SourceImageCache *sourcecache);
536 This method generates all the textures
538 u32 TextureSource::getTextureIdDirect(const std::string &name)
540 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
542 // Empty name means texture 0
545 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
550 Calling only allowed from main thread
552 if(get_current_thread_id() != m_main_thread)
554 errorstream<<"TextureSource::getTextureIdDirect() "
555 "called not from main thread"<<std::endl;
560 See if texture already exists
563 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
565 core::map<std::string, u32>::Node *n;
566 n = m_name_to_id.find(name);
569 /*infostream<<"getTextureIdDirect(): \""<<name
570 <<"\" found in cache"<<std::endl;*/
571 return n->getValue();
575 /*infostream<<"getTextureIdDirect(): \""<<name
576 <<"\" NOT found in cache. Creating it."<<std::endl;*/
582 char separator = '^';
585 This is set to the id of the base image.
586 If left 0, there is no base image and a completely new image
589 u32 base_image_id = 0;
591 // Find last meta separator in name
592 s32 last_separator_position = -1;
593 for(s32 i=name.size()-1; i>=0; i--)
595 if(name[i] == separator)
597 last_separator_position = i;
602 If separator was found, construct the base name and make the
603 base image using a recursive call
605 std::string base_image_name;
606 if(last_separator_position != -1)
608 // Construct base name
609 base_image_name = name.substr(0, last_separator_position);
610 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
611 " to get base image of \""<<name<<"\" = \""
612 <<base_image_name<<"\""<<std::endl;*/
613 base_image_id = getTextureIdDirect(base_image_name);
616 //infostream<<"base_image_id="<<base_image_id<<std::endl;
618 video::IVideoDriver* driver = m_device->getVideoDriver();
621 video::ITexture *t = NULL;
624 An image will be built from files and then converted into a texture.
626 video::IImage *baseimg = NULL;
628 // If a base image was found, copy it to baseimg
629 if(base_image_id != 0)
631 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
633 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
635 video::IImage *image = ap.atlas_img;
639 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
640 <<"cache: \""<<base_image_name<<"\""
645 core::dimension2d<u32> dim = ap.intsize;
647 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
649 core::position2d<s32> pos_to(0,0);
650 core::position2d<s32> pos_from = ap.intpos;
654 v2s32(0,0), // position in target
655 core::rect<s32>(pos_from, dim) // from
658 /*infostream<<"getTextureIdDirect(): Loaded \""
659 <<base_image_name<<"\" from image cache"
665 Parse out the last part of the name of the image and act
669 std::string last_part_of_name = name.substr(last_separator_position+1);
670 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
672 // Generate image according to part of name
673 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
675 errorstream<<"getTextureIdDirect(): "
676 "failed to generate \""<<last_part_of_name<<"\""
680 // If no resulting image, print a warning
683 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
684 " create texture \""<<name<<"\""<<std::endl;
689 // Create texture from resulting image
690 t = driver->addTexture(name.c_str(), baseimg);
694 Add texture to caches (add NULL textures too)
697 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
699 u32 id = m_atlaspointer_cache.size();
705 core::dimension2d<u32> baseimg_dim(0,0);
707 baseimg_dim = baseimg->getDimension();
708 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
709 m_atlaspointer_cache.push_back(nap);
710 m_name_to_id.insert(name, id);
712 /*infostream<<"getTextureIdDirect(): "
713 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
718 std::string TextureSource::getTextureName(u32 id)
720 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
722 if(id >= m_atlaspointer_cache.size())
724 errorstream<<"TextureSource::getTextureName(): id="<<id
725 <<" >= m_atlaspointer_cache.size()="
726 <<m_atlaspointer_cache.size()<<std::endl;
730 return m_atlaspointer_cache[id].name;
734 AtlasPointer TextureSource::getTexture(u32 id)
736 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
738 if(id >= m_atlaspointer_cache.size())
739 return AtlasPointer(0, NULL);
741 return m_atlaspointer_cache[id].a;
744 void TextureSource::updateAP(AtlasPointer &ap)
746 AtlasPointer ap2 = getTexture(ap.id);
750 void TextureSource::processQueue()
755 if(m_get_texture_queue.size() > 0)
757 GetRequest<std::string, u32, u8, u8>
758 request = m_get_texture_queue.pop();
760 /*infostream<<"TextureSource::processQueue(): "
761 <<"got texture request with "
762 <<"name=\""<<request.key<<"\""
765 GetResult<std::string, u32, u8, u8>
767 result.key = request.key;
768 result.callers = request.callers;
769 result.item = getTextureIdDirect(request.key);
771 request.dest->push_back(result);
775 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
777 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
779 assert(get_current_thread_id() == m_main_thread);
781 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
784 void TextureSource::rebuildImagesAndTextures()
786 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
788 /*// Oh well... just clear everything, they'll load sometime.
789 m_atlaspointer_cache.clear();
790 m_name_to_id.clear();*/
792 video::IVideoDriver* driver = m_device->getVideoDriver();
794 // Remove source images from textures to disable inheriting textures
795 // from existing textures
796 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
797 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
798 sap->atlas_img->drop();
799 sap->atlas_img = NULL;
803 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
804 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
806 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
807 // Create texture from resulting image
808 video::ITexture *t = NULL;
810 t = driver->addTexture(sap->name.c_str(), img);
814 sap->a.pos = v2f(0,0);
815 sap->a.size = v2f(1,1);
817 sap->atlas_img = img;
818 sap->intpos = v2s32(0,0);
819 sap->intsize = img->getDimension();
823 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
825 assert(gamedef->tsrc() == this);
826 INodeDefManager *ndef = gamedef->ndef();
828 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
830 //return; // Disable (for testing)
832 video::IVideoDriver* driver = m_device->getVideoDriver();
835 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
837 // Create an image of the right size
838 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
839 core::dimension2d<u32> atlas_dim(2048,2048);
840 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
841 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
842 video::IImage *atlas_img =
843 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
845 if(atlas_img == NULL)
847 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
848 "image; not building texture atlas."<<std::endl;
853 Grab list of stuff to include in the texture atlas from the
854 main content features
857 core::map<std::string, bool> sourcelist;
859 for(u16 j=0; j<MAX_CONTENT+1; j++)
861 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
863 const ContentFeatures &f = ndef->get(j);
864 for(u32 i=0; i<6; i++)
866 std::string name = f.tiledef[i].name;
867 sourcelist[name] = true;
871 infostream<<"Creating texture atlas out of textures: ";
872 for(core::map<std::string, bool>::Iterator
873 i = sourcelist.getIterator();
874 i.atEnd() == false; i++)
876 std::string name = i.getNode()->getKey();
877 infostream<<"\""<<name<<"\" ";
879 infostream<<std::endl;
881 // Padding to disallow texture bleeding
882 // (16 needed if mipmapping is used; otherwise less will work too)
884 s32 column_padding = 16;
885 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
888 First pass: generate almost everything
890 core::position2d<s32> pos_in_atlas(0,0);
892 pos_in_atlas.X = column_padding;
893 pos_in_atlas.Y = padding;
895 for(core::map<std::string, bool>::Iterator
896 i = sourcelist.getIterator();
897 i.atEnd() == false; i++)
899 std::string name = i.getNode()->getKey();
901 // Generate image by name
902 video::IImage *img2 = generate_image_from_scratch(name, m_device,
906 errorstream<<"TextureSource::buildMainAtlas(): "
907 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
911 core::dimension2d<u32> dim = img2->getDimension();
913 // Don't add to atlas if image is too large
914 core::dimension2d<u32> max_size_in_atlas(64,64);
915 if(dim.Width > max_size_in_atlas.Width
916 || dim.Height > max_size_in_atlas.Height)
918 infostream<<"TextureSource::buildMainAtlas(): Not adding "
919 <<"\""<<name<<"\" because image is large"<<std::endl;
923 // Wrap columns and stop making atlas if atlas is full
924 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
926 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
927 errorstream<<"TextureSource::buildMainAtlas(): "
928 <<"Atlas is full, not adding more textures."
932 pos_in_atlas.Y = padding;
933 pos_in_atlas.X += column_width + column_padding*2;
936 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
937 <<"\" to texture atlas"<<std::endl;*/
939 // Tile it a few times in the X direction
940 u16 xwise_tiling = column_width / dim.Width;
941 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
943 for(u32 j=0; j<xwise_tiling; j++)
945 // Copy the copy to the atlas
946 /*img2->copyToWithAlpha(atlas_img,
947 pos_in_atlas + v2s32(j*dim.Width,0),
948 core::rect<s32>(v2s32(0,0), dim),
949 video::SColor(255,255,255,255),
951 img2->copyTo(atlas_img,
952 pos_in_atlas + v2s32(j*dim.Width,0),
953 core::rect<s32>(v2s32(0,0), dim),
957 // Copy the borders a few times to disallow texture bleeding
958 for(u32 side=0; side<2; side++) // top and bottom
959 for(s32 y0=0; y0<padding; y0++)
960 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
966 dst_y = y0 + pos_in_atlas.Y + dim.Height;
967 src_y = pos_in_atlas.Y + dim.Height - 1;
971 dst_y = -y0 + pos_in_atlas.Y-1;
972 src_y = pos_in_atlas.Y;
974 s32 x = x0 + pos_in_atlas.X;
975 video::SColor c = atlas_img->getPixel(x, src_y);
976 atlas_img->setPixel(x,dst_y,c);
979 for(u32 side=0; side<2; side++) // left and right
980 for(s32 x0=0; x0<column_padding; x0++)
981 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
987 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
988 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
992 dst_x = -x0 + pos_in_atlas.X-1;
993 src_x = pos_in_atlas.X;
995 s32 y = y0 + pos_in_atlas.Y;
996 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
998 video::SColor c = atlas_img->getPixel(src_x, src_y);
999 atlas_img->setPixel(dst_x,dst_y,c);
1005 Add texture to caches
1008 bool reuse_old_id = false;
1009 u32 id = m_atlaspointer_cache.size();
1010 // Check old id without fetching a texture
1011 core::map<std::string, u32>::Node *n;
1012 n = m_name_to_id.find(name);
1013 // If it exists, we will replace the old definition
1016 reuse_old_id = true;
1017 /*infostream<<"TextureSource::buildMainAtlas(): "
1018 <<"Replacing old AtlasPointer"<<std::endl;*/
1021 // Create AtlasPointer
1022 AtlasPointer ap(id);
1023 ap.atlas = NULL; // Set on the second pass
1024 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1025 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1026 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1027 (float)dim.Width/(float)atlas_dim.Height);
1028 ap.tiled = xwise_tiling;
1030 // Create SourceAtlasPointer and add to containers
1031 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1033 m_atlaspointer_cache[id] = nap;
1035 m_atlaspointer_cache.push_back(nap);
1036 m_name_to_id[name] = id;
1038 // Increment position
1039 pos_in_atlas.Y += dim.Height + padding * 2;
1045 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1049 Second pass: set texture pointer in generated AtlasPointers
1051 for(core::map<std::string, bool>::Iterator
1052 i = sourcelist.getIterator();
1053 i.atEnd() == false; i++)
1055 std::string name = i.getNode()->getKey();
1056 if(m_name_to_id.find(name) == NULL)
1058 u32 id = m_name_to_id[name];
1059 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1060 m_atlaspointer_cache[id].a.atlas = t;
1064 Write image to file so that it can be inspected
1066 /*std::string atlaspath = porting::path_user
1067 + DIR_DELIM + "generated_texture_atlas.png";
1068 infostream<<"Removing and writing texture atlas for inspection to "
1069 <<atlaspath<<std::endl;
1070 fs::RecursiveDelete(atlaspath);
1071 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1074 video::IImage* generate_image_from_scratch(std::string name,
1075 IrrlichtDevice *device, SourceImageCache *sourcecache)
1077 /*infostream<<"generate_image_from_scratch(): "
1078 "\""<<name<<"\""<<std::endl;*/
1080 video::IVideoDriver* driver = device->getVideoDriver();
1087 video::IImage *baseimg = NULL;
1089 char separator = '^';
1091 // Find last meta separator in name
1092 s32 last_separator_position = name.find_last_of(separator);
1093 //if(last_separator_position == std::npos)
1094 // last_separator_position = -1;
1096 /*infostream<<"generate_image_from_scratch(): "
1097 <<"last_separator_position="<<last_separator_position
1101 If separator was found, construct the base name and make the
1102 base image using a recursive call
1104 std::string base_image_name;
1105 if(last_separator_position != -1)
1107 // Construct base name
1108 base_image_name = name.substr(0, last_separator_position);
1109 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1110 " to get base image of \""<<name<<"\" = \""
1111 <<base_image_name<<"\""<<std::endl;*/
1112 baseimg = generate_image_from_scratch(base_image_name, device,
1117 Parse out the last part of the name of the image and act
1121 std::string last_part_of_name = name.substr(last_separator_position+1);
1122 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1124 // Generate image according to part of name
1125 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1127 errorstream<<"generate_image_from_scratch(): "
1128 "failed to generate \""<<last_part_of_name<<"\""
1136 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1137 IrrlichtDevice *device, SourceImageCache *sourcecache)
1139 video::IVideoDriver* driver = device->getVideoDriver();
1142 // Stuff starting with [ are special commands
1143 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1145 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1149 if(part_of_name != ""){
1150 errorstream<<"generate_image(): Could not load image \""
1151 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1152 errorstream<<"generate_image(): Creating a dummy"
1153 <<" image for \""<<part_of_name<<"\""<<std::endl;
1156 // Just create a dummy image
1157 //core::dimension2d<u32> dim(2,2);
1158 core::dimension2d<u32> dim(1,1);
1159 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1161 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1162 image->setPixel(1,0, video::SColor(255,0,255,0));
1163 image->setPixel(0,1, video::SColor(255,0,0,255));
1164 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1165 image->setPixel(0,0, video::SColor(255,myrand()%256,
1166 myrand()%256,myrand()%256));
1167 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1168 myrand()%256,myrand()%256));
1169 image->setPixel(0,1, video::SColor(255,myrand()%256,
1170 myrand()%256,myrand()%256));
1171 image->setPixel(1,1, video::SColor(255,myrand()%256,
1172 myrand()%256,myrand()%256));*/
1175 // If base image is NULL, load as base.
1178 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1180 Copy it this way to get an alpha channel.
1181 Otherwise images with alpha cannot be blitted on
1182 images that don't have alpha in the original file.
1184 core::dimension2d<u32> dim = image->getDimension();
1185 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1186 image->copyTo(baseimg);
1189 // Else blit on base.
1192 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1193 // Size of the copied area
1194 core::dimension2d<u32> dim = image->getDimension();
1195 //core::dimension2d<u32> dim(16,16);
1196 // Position to copy the blitted to in the base image
1197 core::position2d<s32> pos_to(0,0);
1198 // Position to copy the blitted from in the blitted image
1199 core::position2d<s32> pos_from(0,0);
1201 image->copyToWithAlpha(baseimg, pos_to,
1202 core::rect<s32>(pos_from, dim),
1203 video::SColor(255,255,255,255),
1211 // A special texture modification
1213 /*infostream<<"generate_image(): generating special "
1214 <<"modification \""<<part_of_name<<"\""
1218 This is the simplest of all; it just adds stuff to the
1219 name so that a separate texture is created.
1221 It is used to make textures for stuff that doesn't want
1222 to implement getting the texture from a bigger texture
1225 if(part_of_name == "[forcesingle")
1227 // If base image is NULL, create a random color
1230 core::dimension2d<u32> dim(1,1);
1231 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1233 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1234 myrand()%256,myrand()%256));
1239 Adds a cracking texture
1241 else if(part_of_name.substr(0,6) == "[crack")
1245 errorstream<<"generate_image(): baseimg==NULL "
1246 <<"for part_of_name=\""<<part_of_name
1247 <<"\", cancelling."<<std::endl;
1251 // Crack image number and overlay option
1252 s32 progression = 0;
1253 bool use_overlay = false;
1254 if(part_of_name.substr(6,1) == "o")
1256 progression = stoi(part_of_name.substr(7));
1261 progression = stoi(part_of_name.substr(6));
1262 use_overlay = false;
1265 // Size of the base image
1266 core::dimension2d<u32> dim_base = baseimg->getDimension();
1271 It is an image with a number of cracking stages
1274 video::IImage *img_crack = sourcecache->getOrLoad(
1275 "crack_anylength.png", device);
1277 if(img_crack && progression >= 0)
1279 // Dimension of original image
1280 core::dimension2d<u32> dim_crack
1281 = img_crack->getDimension();
1282 // Count of crack stages
1283 s32 crack_count = dim_crack.Height / dim_crack.Width;
1284 // Limit progression
1285 if(progression > crack_count-1)
1286 progression = crack_count-1;
1287 // Dimension of a single crack stage
1288 core::dimension2d<u32> dim_crack_cropped(
1292 // Create cropped and scaled crack images
1293 video::IImage *img_crack_cropped = driver->createImage(
1294 video::ECF_A8R8G8B8, dim_crack_cropped);
1295 video::IImage *img_crack_scaled = driver->createImage(
1296 video::ECF_A8R8G8B8, dim_base);
1298 if(img_crack_cropped && img_crack_scaled)
1301 v2s32 pos_crack(0, progression*dim_crack.Width);
1302 img_crack->copyTo(img_crack_cropped,
1304 core::rect<s32>(pos_crack, dim_crack_cropped));
1305 // Scale crack image by copying
1306 img_crack_cropped->copyToScaling(img_crack_scaled);
1307 // Copy or overlay crack image
1310 overlay(baseimg, img_crack_scaled);
1314 /*img_crack_scaled->copyToWithAlpha(
1317 core::rect<s32>(v2s32(0,0), dim_base),
1318 video::SColor(255,255,255,255));*/
1319 blit_with_alpha(img_crack_scaled, baseimg,
1320 v2s32(0,0), v2s32(0,0), dim_base);
1324 if(img_crack_scaled)
1325 img_crack_scaled->drop();
1327 if(img_crack_cropped)
1328 img_crack_cropped->drop();
1334 [combine:WxH:X,Y=filename:X,Y=filename2
1335 Creates a bigger texture from an amount of smaller ones
1337 else if(part_of_name.substr(0,8) == "[combine")
1339 Strfnd sf(part_of_name);
1341 u32 w0 = stoi(sf.next("x"));
1342 u32 h0 = stoi(sf.next(":"));
1343 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1344 core::dimension2d<u32> dim(w0,h0);
1345 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1346 while(sf.atend() == false)
1348 u32 x = stoi(sf.next(","));
1349 u32 y = stoi(sf.next("="));
1350 std::string filename = sf.next(":");
1351 infostream<<"Adding \""<<filename
1352 <<"\" to combined ("<<x<<","<<y<<")"
1354 video::IImage *img = sourcecache->getOrLoad(filename, device);
1357 core::dimension2d<u32> dim = img->getDimension();
1358 infostream<<"Size "<<dim.Width
1359 <<"x"<<dim.Height<<std::endl;
1360 core::position2d<s32> pos_base(x, y);
1361 video::IImage *img2 =
1362 driver->createImage(video::ECF_A8R8G8B8, dim);
1365 img2->copyToWithAlpha(baseimg, pos_base,
1366 core::rect<s32>(v2s32(0,0), dim),
1367 video::SColor(255,255,255,255),
1373 infostream<<"img==NULL"<<std::endl;
1380 else if(part_of_name.substr(0,9) == "[brighten")
1384 errorstream<<"generate_image(): baseimg==NULL "
1385 <<"for part_of_name=\""<<part_of_name
1386 <<"\", cancelling."<<std::endl;
1394 Make image completely opaque.
1395 Used for the leaves texture when in old leaves mode, so
1396 that the transparent parts don't look completely black
1397 when simple alpha channel is used for rendering.
1399 else if(part_of_name.substr(0,8) == "[noalpha")
1403 errorstream<<"generate_image(): baseimg==NULL "
1404 <<"for part_of_name=\""<<part_of_name
1405 <<"\", cancelling."<<std::endl;
1409 core::dimension2d<u32> dim = baseimg->getDimension();
1411 // Set alpha to full
1412 for(u32 y=0; y<dim.Height; y++)
1413 for(u32 x=0; x<dim.Width; x++)
1415 video::SColor c = baseimg->getPixel(x,y);
1417 baseimg->setPixel(x,y,c);
1422 Convert one color to transparent.
1424 else if(part_of_name.substr(0,11) == "[makealpha:")
1428 errorstream<<"generate_image(): baseimg==NULL "
1429 <<"for part_of_name=\""<<part_of_name
1430 <<"\", cancelling."<<std::endl;
1434 Strfnd sf(part_of_name.substr(11));
1435 u32 r1 = stoi(sf.next(","));
1436 u32 g1 = stoi(sf.next(","));
1437 u32 b1 = stoi(sf.next(""));
1438 std::string filename = sf.next("");
1440 core::dimension2d<u32> dim = baseimg->getDimension();
1442 /*video::IImage *oldbaseimg = baseimg;
1443 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1444 oldbaseimg->copyTo(baseimg);
1445 oldbaseimg->drop();*/
1447 // Set alpha to full
1448 for(u32 y=0; y<dim.Height; y++)
1449 for(u32 x=0; x<dim.Width; x++)
1451 video::SColor c = baseimg->getPixel(x,y);
1453 u32 g = c.getGreen();
1454 u32 b = c.getBlue();
1455 if(!(r == r1 && g == g1 && b == b1))
1458 baseimg->setPixel(x,y,c);
1463 Rotates and/or flips the image.
1465 N can be a number (between 0 and 7) or a transform name.
1466 Rotations are counter-clockwise.
1468 1 R90 rotate by 90 degrees
1469 2 R180 rotate by 180 degrees
1470 3 R270 rotate by 270 degrees
1472 5 FXR90 flip X then rotate by 90 degrees
1474 7 FYR90 flip Y then rotate by 90 degrees
1476 Note: Transform names can be concatenated to produce
1477 their product (applies the first then the second).
1478 The resulting transform will be equivalent to one of the
1479 eight existing ones, though (see: dihedral group).
1481 else if(part_of_name.substr(0,10) == "[transform")
1485 errorstream<<"generate_image(): baseimg==NULL "
1486 <<"for part_of_name=\""<<part_of_name
1487 <<"\", cancelling."<<std::endl;
1491 u32 transform = parseImageTransform(part_of_name.substr(10));
1492 core::dimension2d<u32> dim = imageTransformDimension(
1493 transform, baseimg->getDimension());
1494 video::IImage *image = driver->createImage(
1495 baseimg->getColorFormat(), dim);
1497 imageTransform(transform, baseimg, image);
1502 [inventorycube{topimage{leftimage{rightimage
1503 In every subimage, replace ^ with &.
1504 Create an "inventory cube".
1505 NOTE: This should be used only on its own.
1506 Example (a grass block (not actually used in game):
1507 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1509 else if(part_of_name.substr(0,14) == "[inventorycube")
1513 errorstream<<"generate_image(): baseimg!=NULL "
1514 <<"for part_of_name=\""<<part_of_name
1515 <<"\", cancelling."<<std::endl;
1519 str_replace_char(part_of_name, '&', '^');
1520 Strfnd sf(part_of_name);
1522 std::string imagename_top = sf.next("{");
1523 std::string imagename_left = sf.next("{");
1524 std::string imagename_right = sf.next("{");
1526 // Generate images for the faces of the cube
1527 video::IImage *img_top = generate_image_from_scratch(
1528 imagename_top, device, sourcecache);
1529 video::IImage *img_left = generate_image_from_scratch(
1530 imagename_left, device, sourcecache);
1531 video::IImage *img_right = generate_image_from_scratch(
1532 imagename_right, device, sourcecache);
1533 assert(img_top && img_left && img_right);
1535 // Create textures from images
1536 video::ITexture *texture_top = driver->addTexture(
1537 (imagename_top + "__temp__").c_str(), img_top);
1538 video::ITexture *texture_left = driver->addTexture(
1539 (imagename_left + "__temp__").c_str(), img_left);
1540 video::ITexture *texture_right = driver->addTexture(
1541 (imagename_right + "__temp__").c_str(), img_right);
1542 assert(texture_top && texture_left && texture_right);
1550 Draw a cube mesh into a render target texture
1552 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1553 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1554 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1555 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1556 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1557 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1558 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1559 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1561 core::dimension2d<u32> dim(64,64);
1562 std::string rtt_texture_name = part_of_name + "_RTT";
1564 v3f camera_position(0, 1.0, -1.5);
1565 camera_position.rotateXZBy(45);
1566 v3f camera_lookat(0, 0, 0);
1567 core::CMatrix4<f32> camera_projection_matrix;
1568 // Set orthogonal projection
1569 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1570 1.65, 1.65, 0, 100);
1572 video::SColorf ambient_light(0.2,0.2,0.2);
1573 v3f light_position(10, 100, -50);
1574 video::SColorf light_color(0.5,0.5,0.5);
1575 f32 light_radius = 1000;
1577 video::ITexture *rtt = generateTextureFromMesh(
1578 cube, device, dim, rtt_texture_name,
1581 camera_projection_matrix,
1590 // Free textures of images
1591 driver->removeTexture(texture_top);
1592 driver->removeTexture(texture_left);
1593 driver->removeTexture(texture_right);
1597 baseimg = generate_image_from_scratch(
1598 imagename_top, device, sourcecache);
1602 // Create image of render target
1603 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1606 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1610 image->copyTo(baseimg);
1615 [lowpart:percent:filename
1616 Adds the lower part of a texture
1618 else if(part_of_name.substr(0,9) == "[lowpart:")
1620 Strfnd sf(part_of_name);
1622 u32 percent = stoi(sf.next(":"));
1623 std::string filename = sf.next(":");
1624 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1627 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1628 video::IImage *img = sourcecache->getOrLoad(filename, device);
1631 core::dimension2d<u32> dim = img->getDimension();
1632 core::position2d<s32> pos_base(0, 0);
1633 video::IImage *img2 =
1634 driver->createImage(video::ECF_A8R8G8B8, dim);
1637 core::position2d<s32> clippos(0, 0);
1638 clippos.Y = dim.Height * (100-percent) / 100;
1639 core::dimension2d<u32> clipdim = dim;
1640 clipdim.Height = clipdim.Height * percent / 100 + 1;
1641 core::rect<s32> cliprect(clippos, clipdim);
1642 img2->copyToWithAlpha(baseimg, pos_base,
1643 core::rect<s32>(v2s32(0,0), dim),
1644 video::SColor(255,255,255,255),
1651 Crops a frame of a vertical animation.
1652 N = frame count, I = frame index
1654 else if(part_of_name.substr(0,15) == "[verticalframe:")
1656 Strfnd sf(part_of_name);
1658 u32 frame_count = stoi(sf.next(":"));
1659 u32 frame_index = stoi(sf.next(":"));
1661 if(baseimg == NULL){
1662 errorstream<<"generate_image(): baseimg!=NULL "
1663 <<"for part_of_name=\""<<part_of_name
1664 <<"\", cancelling."<<std::endl;
1668 v2u32 frame_size = baseimg->getDimension();
1669 frame_size.Y /= frame_count;
1671 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1674 errorstream<<"generate_image(): Could not create image "
1675 <<"for part_of_name=\""<<part_of_name
1676 <<"\", cancelling."<<std::endl;
1680 core::dimension2d<u32> dim = frame_size;
1681 core::position2d<s32> pos_dst(0, 0);
1682 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1683 baseimg->copyToWithAlpha(img, pos_dst,
1684 core::rect<s32>(pos_src, dim),
1685 video::SColor(255,255,255,255),
1693 errorstream<<"generate_image(): Invalid "
1694 " modification: \""<<part_of_name<<"\""<<std::endl;
1701 void overlay(video::IImage *image, video::IImage *overlay)
1704 Copy overlay to image, taking alpha into account.
1705 Where image is transparent, don't copy from overlay.
1706 Images sizes must be identical.
1708 if(image == NULL || overlay == NULL)
1711 core::dimension2d<u32> dim = image->getDimension();
1712 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1713 assert(dim == dim_overlay);
1715 for(u32 y=0; y<dim.Height; y++)
1716 for(u32 x=0; x<dim.Width; x++)
1718 video::SColor c1 = image->getPixel(x,y);
1719 video::SColor c2 = overlay->getPixel(x,y);
1720 u32 a1 = c1.getAlpha();
1721 u32 a2 = c2.getAlpha();
1722 if(a1 == 255 && a2 != 0)
1724 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1725 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1726 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1728 image->setPixel(x,y,c1);
1733 Draw an image on top of an another one, using the alpha channel of the
1736 This exists because IImage::copyToWithAlpha() doesn't seem to always
1739 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1740 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1742 for(u32 y0=0; y0<size.Y; y0++)
1743 for(u32 x0=0; x0<size.X; x0++)
1745 s32 src_x = src_pos.X + x0;
1746 s32 src_y = src_pos.Y + y0;
1747 s32 dst_x = dst_pos.X + x0;
1748 s32 dst_y = dst_pos.Y + y0;
1749 video::SColor src_c = src->getPixel(src_x, src_y);
1750 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1751 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1752 dst->setPixel(dst_x, dst_y, dst_c);
1756 void brighten(video::IImage *image)
1761 core::dimension2d<u32> dim = image->getDimension();
1763 for(u32 y=0; y<dim.Height; y++)
1764 for(u32 x=0; x<dim.Width; x++)
1766 video::SColor c = image->getPixel(x,y);
1767 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1768 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1769 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1770 image->setPixel(x,y,c);
1774 u32 parseImageTransform(const std::string& s)
1776 int total_transform = 0;
1778 std::string transform_names[8];
1779 transform_names[0] = "i";
1780 transform_names[1] = "r90";
1781 transform_names[2] = "r180";
1782 transform_names[3] = "r270";
1783 transform_names[4] = "fx";
1784 transform_names[6] = "fy";
1786 std::size_t pos = 0;
1787 while(pos < s.size())
1790 for(int i = 0; i <= 7; ++i)
1792 const std::string &name_i = transform_names[i];
1794 if(s[pos] == ('0' + i))
1800 else if(!(name_i.empty()) &&
1801 lowercase(s.substr(pos, name_i.size())) == name_i)
1804 pos += name_i.size();
1811 // Multiply total_transform and transform in the group D4
1814 new_total = (transform + total_transform) % 4;
1816 new_total = (transform - total_transform + 8) % 4;
1817 if((transform >= 4) ^ (total_transform >= 4))
1820 total_transform = new_total;
1822 return total_transform;
1825 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1827 if(transform % 2 == 0)
1830 return core::dimension2d<u32>(dim.Height, dim.Width);
1833 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1835 if(src == NULL || dst == NULL)
1838 core::dimension2d<u32> srcdim = src->getDimension();
1839 core::dimension2d<u32> dstdim = dst->getDimension();
1841 assert(dstdim == imageTransformDimension(transform, srcdim));
1842 assert(transform >= 0 && transform <= 7);
1845 Compute the transformation from source coordinates (sx,sy)
1846 to destination coordinates (dx,dy).
1850 if(transform == 0) // identity
1851 sxn = 0, syn = 2; // sx = dx, sy = dy
1852 else if(transform == 1) // rotate by 90 degrees ccw
1853 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1854 else if(transform == 2) // rotate by 180 degrees
1855 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1856 else if(transform == 3) // rotate by 270 degrees ccw
1857 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1858 else if(transform == 4) // flip x
1859 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1860 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1861 sxn = 2, syn = 0; // sx = dy, sy = dx
1862 else if(transform == 6) // flip y
1863 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1864 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1865 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1867 for(u32 dy=0; dy<dstdim.Height; dy++)
1868 for(u32 dx=0; dx<dstdim.Width; dx++)
1870 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1871 u32 sx = entries[sxn];
1872 u32 sy = entries[syn];
1873 video::SColor c = src->getPixel(sx,sy);
1874 dst->setPixel(dx,dy,c);