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
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 void insert(const std::string &name, video::IImage *img,
205 bool prefer_local, video::IVideoDriver *driver)
209 core::map<std::string, video::IImage*>::Node *n;
210 n = m_images.find(name);
212 video::IImage *oldimg = n->getValue();
216 // Try to use local texture instead if asked to
218 std::string path = getTexturePath(name.c_str());
220 video::IImage *img2 = driver->createImageFromFile(path.c_str());
222 m_images[name] = img2;
228 m_images[name] = img;
230 video::IImage* get(const std::string &name)
232 core::map<std::string, video::IImage*>::Node *n;
233 n = m_images.find(name);
235 return n->getValue();
238 // Primarily fetches from cache, secondarily tries to read from filesystem
239 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
241 core::map<std::string, video::IImage*>::Node *n;
242 n = m_images.find(name);
244 n->getValue()->grab(); // Grab for caller
245 return n->getValue();
247 video::IVideoDriver* driver = device->getVideoDriver();
248 std::string path = getTexturePath(name.c_str());
250 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
251 <<name<<"\""<<std::endl;
254 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
256 video::IImage *img = driver->createImageFromFile(path.c_str());
257 // Even if could not be loaded, put as NULL
258 //m_images[name] = img;
260 m_images[name] = img;
261 img->grab(); // Grab for caller
266 core::map<std::string, video::IImage*> m_images;
273 class TextureSource : public IWritableTextureSource
276 TextureSource(IrrlichtDevice *device);
281 Now, assume a texture with the id 1 exists, and has the name
282 "stone.png^mineral1".
283 Then a random thread calls getTextureId for a texture called
284 "stone.png^mineral1^crack0".
285 ...Now, WTF should happen? Well:
286 - getTextureId strips off stuff recursively from the end until
287 the remaining part is found, or nothing is left when
288 something is stripped out
290 But it is slow to search for textures by names and modify them
292 - ContentFeatures is made to contain ids for the basic plain
294 - Crack textures can be slow by themselves, but the framework
298 - Assume a texture with the id 1 exists, and has the name
299 "stone.png^mineral1" and is specified as a part of some atlas.
300 - Now getNodeTile() stumbles upon a node which uses
301 texture id 1, and determines that MATERIAL_FLAG_CRACK
302 must be applied to the tile
303 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
304 has received the current crack level 0 from the client. It
305 finds out the name of the texture with getTextureName(1),
306 appends "^crack0" to it and gets a new texture id with
307 getTextureId("stone.png^mineral1^crack0").
312 Gets a texture id from cache or
313 - if main thread, from getTextureIdDirect
314 - if other thread, adds to request queue and waits for main thread
316 u32 getTextureId(const std::string &name);
322 "stone.png^mineral_coal.png"
323 "stone.png^mineral_coal.png^crack1"
325 - If texture specified by name is found from cache, return the
327 - Otherwise generate the texture, add to cache and return id.
328 Recursion is used to find out the largest found part of the
329 texture and continue based on it.
331 The id 0 points to a NULL texture. It is returned in case of error.
333 u32 getTextureIdDirect(const std::string &name);
335 // Finds out the name of a cached texture.
336 std::string getTextureName(u32 id);
339 If texture specified by the name pointed by the id doesn't
340 exist, create it, then return the cached texture.
342 Can be called from any thread. If called from some other thread
343 and not found in cache, the call is queued to the main thread
346 AtlasPointer getTexture(u32 id);
348 AtlasPointer getTexture(const std::string &name)
350 return getTexture(getTextureId(name));
353 // Gets a separate texture
354 video::ITexture* getTextureRaw(const std::string &name)
356 AtlasPointer ap = getTexture(name + "^[forcesingle");
360 // Gets a separate texture atlas pointer
361 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
363 return getTexture(getTextureName(ap.id) + "^[forcesingle");
366 // Returns a pointer to the irrlicht device
367 virtual IrrlichtDevice* getDevice()
372 // Update new texture pointer and texture coordinates to an
373 // AtlasPointer based on it's texture id
374 void updateAP(AtlasPointer &ap);
376 // Processes queued texture requests from other threads.
377 // Shall be called from the main thread.
380 // Insert an image into the cache without touching the filesystem.
381 // Shall be called from the main thread.
382 void insertSourceImage(const std::string &name, video::IImage *img);
384 // Rebuild images and textures from the current set of source images
385 // Shall be called from the main thread.
386 void rebuildImagesAndTextures();
388 // Build the main texture atlas which contains most of the
390 void buildMainAtlas(class IGameDef *gamedef);
394 // The id of the thread that is allowed to use irrlicht directly
395 threadid_t m_main_thread;
396 // The irrlicht device
397 IrrlichtDevice *m_device;
399 // Cache of source images
400 // This should be only accessed from the main thread
401 SourceImageCache m_sourcecache;
403 // A texture id is index in this array.
404 // The first position contains a NULL texture.
405 core::array<SourceAtlasPointer> m_atlaspointer_cache;
406 // Maps a texture name to an index in the former.
407 core::map<std::string, u32> m_name_to_id;
408 // The two former containers are behind this mutex
409 JMutex m_atlaspointer_cache_mutex;
411 // Main texture atlas. This is filled at startup and is then not touched.
412 video::IImage *m_main_atlas_image;
413 video::ITexture *m_main_atlas_texture;
415 // Queued texture fetches (to be processed by the main thread)
416 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
419 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
421 return new TextureSource(device);
424 TextureSource::TextureSource(IrrlichtDevice *device):
426 m_main_atlas_image(NULL),
427 m_main_atlas_texture(NULL)
431 m_atlaspointer_cache_mutex.Init();
433 m_main_thread = get_current_thread_id();
435 // Add a NULL AtlasPointer as the first index, named ""
436 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
437 m_name_to_id[""] = 0;
440 TextureSource::~TextureSource()
444 u32 TextureSource::getTextureId(const std::string &name)
446 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
450 See if texture already exists
452 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
453 core::map<std::string, u32>::Node *n;
454 n = m_name_to_id.find(name);
457 return n->getValue();
464 if(get_current_thread_id() == m_main_thread)
466 return getTextureIdDirect(name);
470 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
472 // We're gonna ask the result to be put into here
473 ResultQueue<std::string, u32, u8, u8> result_queue;
475 // Throw a request in
476 m_get_texture_queue.add(name, 0, 0, &result_queue);
478 infostream<<"Waiting for texture from main thread, name=\""
479 <<name<<"\""<<std::endl;
483 // Wait result for a second
484 GetResult<std::string, u32, u8, u8>
485 result = result_queue.pop_front(1000);
487 // Check that at least something worked OK
488 assert(result.key == name);
492 catch(ItemNotFoundException &e)
494 infostream<<"Waiting for texture timed out."<<std::endl;
499 infostream<<"getTextureId(): Failed"<<std::endl;
504 // Overlay image on top of another image (used for cracks)
505 void overlay(video::IImage *image, video::IImage *overlay);
507 // Draw an image on top of an another one, using the alpha channel of the
509 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
510 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
513 void brighten(video::IImage *image);
514 // Parse a transform name
515 u32 parseImageTransform(const std::string& s);
516 // Apply transform to image dimension
517 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
518 // Apply transform to image data
519 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
522 Generate image based on a string like "stone.png" or "[crack0".
523 if baseimg is NULL, it is created. Otherwise stuff is made on it.
525 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
526 IrrlichtDevice *device, SourceImageCache *sourcecache);
529 Generates an image from a full string like
530 "stone.png^mineral_coal.png^[crack0".
532 This is used by buildMainAtlas().
534 video::IImage* generate_image_from_scratch(std::string name,
535 IrrlichtDevice *device, SourceImageCache *sourcecache);
538 This method generates all the textures
540 u32 TextureSource::getTextureIdDirect(const std::string &name)
542 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
544 // Empty name means texture 0
547 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
552 Calling only allowed from main thread
554 if(get_current_thread_id() != m_main_thread)
556 errorstream<<"TextureSource::getTextureIdDirect() "
557 "called not from main thread"<<std::endl;
562 See if texture already exists
565 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
567 core::map<std::string, u32>::Node *n;
568 n = m_name_to_id.find(name);
571 /*infostream<<"getTextureIdDirect(): \""<<name
572 <<"\" found in cache"<<std::endl;*/
573 return n->getValue();
577 /*infostream<<"getTextureIdDirect(): \""<<name
578 <<"\" NOT found in cache. Creating it."<<std::endl;*/
584 char separator = '^';
587 This is set to the id of the base image.
588 If left 0, there is no base image and a completely new image
591 u32 base_image_id = 0;
593 // Find last meta separator in name
594 s32 last_separator_position = -1;
595 for(s32 i=name.size()-1; i>=0; i--)
597 if(name[i] == separator)
599 last_separator_position = i;
604 If separator was found, construct the base name and make the
605 base image using a recursive call
607 std::string base_image_name;
608 if(last_separator_position != -1)
610 // Construct base name
611 base_image_name = name.substr(0, last_separator_position);
612 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
613 " to get base image of \""<<name<<"\" = \""
614 <<base_image_name<<"\""<<std::endl;*/
615 base_image_id = getTextureIdDirect(base_image_name);
618 //infostream<<"base_image_id="<<base_image_id<<std::endl;
620 video::IVideoDriver* driver = m_device->getVideoDriver();
623 video::ITexture *t = NULL;
626 An image will be built from files and then converted into a texture.
628 video::IImage *baseimg = NULL;
630 // If a base image was found, copy it to baseimg
631 if(base_image_id != 0)
633 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
635 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
637 video::IImage *image = ap.atlas_img;
641 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
642 <<"cache: \""<<base_image_name<<"\""
647 core::dimension2d<u32> dim = ap.intsize;
649 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
651 core::position2d<s32> pos_to(0,0);
652 core::position2d<s32> pos_from = ap.intpos;
656 v2s32(0,0), // position in target
657 core::rect<s32>(pos_from, dim) // from
660 /*infostream<<"getTextureIdDirect(): Loaded \""
661 <<base_image_name<<"\" from image cache"
667 Parse out the last part of the name of the image and act
671 std::string last_part_of_name = name.substr(last_separator_position+1);
672 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
674 // Generate image according to part of name
675 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
677 errorstream<<"getTextureIdDirect(): "
678 "failed to generate \""<<last_part_of_name<<"\""
682 // If no resulting image, print a warning
685 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
686 " create texture \""<<name<<"\""<<std::endl;
691 // Create texture from resulting image
692 t = driver->addTexture(name.c_str(), baseimg);
696 Add texture to caches (add NULL textures too)
699 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
701 u32 id = m_atlaspointer_cache.size();
707 core::dimension2d<u32> baseimg_dim(0,0);
709 baseimg_dim = baseimg->getDimension();
710 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
711 m_atlaspointer_cache.push_back(nap);
712 m_name_to_id.insert(name, id);
714 /*infostream<<"getTextureIdDirect(): "
715 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
720 std::string TextureSource::getTextureName(u32 id)
722 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
724 if(id >= m_atlaspointer_cache.size())
726 errorstream<<"TextureSource::getTextureName(): id="<<id
727 <<" >= m_atlaspointer_cache.size()="
728 <<m_atlaspointer_cache.size()<<std::endl;
732 return m_atlaspointer_cache[id].name;
736 AtlasPointer TextureSource::getTexture(u32 id)
738 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
740 if(id >= m_atlaspointer_cache.size())
741 return AtlasPointer(0, NULL);
743 return m_atlaspointer_cache[id].a;
746 void TextureSource::updateAP(AtlasPointer &ap)
748 AtlasPointer ap2 = getTexture(ap.id);
752 void TextureSource::processQueue()
757 if(m_get_texture_queue.size() > 0)
759 GetRequest<std::string, u32, u8, u8>
760 request = m_get_texture_queue.pop();
762 /*infostream<<"TextureSource::processQueue(): "
763 <<"got texture request with "
764 <<"name=\""<<request.key<<"\""
767 GetResult<std::string, u32, u8, u8>
769 result.key = request.key;
770 result.callers = request.callers;
771 result.item = getTextureIdDirect(request.key);
773 request.dest->push_back(result);
777 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
779 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
781 assert(get_current_thread_id() == m_main_thread);
783 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
786 void TextureSource::rebuildImagesAndTextures()
788 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
790 /*// Oh well... just clear everything, they'll load sometime.
791 m_atlaspointer_cache.clear();
792 m_name_to_id.clear();*/
794 video::IVideoDriver* driver = m_device->getVideoDriver();
796 // Remove source images from textures to disable inheriting textures
797 // from existing textures
798 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
799 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
800 sap->atlas_img->drop();
801 sap->atlas_img = NULL;
805 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
806 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
808 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
809 // Create texture from resulting image
810 video::ITexture *t = NULL;
812 t = driver->addTexture(sap->name.c_str(), img);
816 sap->a.pos = v2f(0,0);
817 sap->a.size = v2f(1,1);
819 sap->atlas_img = img;
820 sap->intpos = v2s32(0,0);
821 sap->intsize = img->getDimension();
825 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
827 assert(gamedef->tsrc() == this);
828 INodeDefManager *ndef = gamedef->ndef();
830 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
832 //return; // Disable (for testing)
834 video::IVideoDriver* driver = m_device->getVideoDriver();
837 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
839 // Create an image of the right size
840 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
841 core::dimension2d<u32> atlas_dim(2048,2048);
842 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
843 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
844 video::IImage *atlas_img =
845 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
847 if(atlas_img == NULL)
849 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
850 "image; not building texture atlas."<<std::endl;
855 Grab list of stuff to include in the texture atlas from the
856 main content features
859 core::map<std::string, bool> sourcelist;
861 for(u16 j=0; j<MAX_CONTENT+1; j++)
863 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
865 const ContentFeatures &f = ndef->get(j);
866 for(u32 i=0; i<6; i++)
868 std::string name = f.tiledef[i].name;
869 sourcelist[name] = true;
873 infostream<<"Creating texture atlas out of textures: ";
874 for(core::map<std::string, bool>::Iterator
875 i = sourcelist.getIterator();
876 i.atEnd() == false; i++)
878 std::string name = i.getNode()->getKey();
879 infostream<<"\""<<name<<"\" ";
881 infostream<<std::endl;
883 // Padding to disallow texture bleeding
884 // (16 needed if mipmapping is used; otherwise less will work too)
886 s32 column_padding = 16;
887 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
890 First pass: generate almost everything
892 core::position2d<s32> pos_in_atlas(0,0);
894 pos_in_atlas.X = column_padding;
895 pos_in_atlas.Y = padding;
897 for(core::map<std::string, bool>::Iterator
898 i = sourcelist.getIterator();
899 i.atEnd() == false; i++)
901 std::string name = i.getNode()->getKey();
903 // Generate image by name
904 video::IImage *img2 = generate_image_from_scratch(name, m_device,
908 errorstream<<"TextureSource::buildMainAtlas(): "
909 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
913 core::dimension2d<u32> dim = img2->getDimension();
915 // Don't add to atlas if image is too large
916 core::dimension2d<u32> max_size_in_atlas(64,64);
917 if(dim.Width > max_size_in_atlas.Width
918 || dim.Height > max_size_in_atlas.Height)
920 infostream<<"TextureSource::buildMainAtlas(): Not adding "
921 <<"\""<<name<<"\" because image is large"<<std::endl;
925 // Wrap columns and stop making atlas if atlas is full
926 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
928 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
929 errorstream<<"TextureSource::buildMainAtlas(): "
930 <<"Atlas is full, not adding more textures."
934 pos_in_atlas.Y = padding;
935 pos_in_atlas.X += column_width + column_padding*2;
938 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
939 <<"\" to texture atlas"<<std::endl;*/
941 // Tile it a few times in the X direction
942 u16 xwise_tiling = column_width / dim.Width;
943 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
945 for(u32 j=0; j<xwise_tiling; j++)
947 // Copy the copy to the atlas
948 /*img2->copyToWithAlpha(atlas_img,
949 pos_in_atlas + v2s32(j*dim.Width,0),
950 core::rect<s32>(v2s32(0,0), dim),
951 video::SColor(255,255,255,255),
953 img2->copyTo(atlas_img,
954 pos_in_atlas + v2s32(j*dim.Width,0),
955 core::rect<s32>(v2s32(0,0), dim),
959 // Copy the borders a few times to disallow texture bleeding
960 for(u32 side=0; side<2; side++) // top and bottom
961 for(s32 y0=0; y0<padding; y0++)
962 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
968 dst_y = y0 + pos_in_atlas.Y + dim.Height;
969 src_y = pos_in_atlas.Y + dim.Height - 1;
973 dst_y = -y0 + pos_in_atlas.Y-1;
974 src_y = pos_in_atlas.Y;
976 s32 x = x0 + pos_in_atlas.X;
977 video::SColor c = atlas_img->getPixel(x, src_y);
978 atlas_img->setPixel(x,dst_y,c);
981 for(u32 side=0; side<2; side++) // left and right
982 for(s32 x0=0; x0<column_padding; x0++)
983 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
989 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
990 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
994 dst_x = -x0 + pos_in_atlas.X-1;
995 src_x = pos_in_atlas.X;
997 s32 y = y0 + pos_in_atlas.Y;
998 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1000 video::SColor c = atlas_img->getPixel(src_x, src_y);
1001 atlas_img->setPixel(dst_x,dst_y,c);
1007 Add texture to caches
1010 bool reuse_old_id = false;
1011 u32 id = m_atlaspointer_cache.size();
1012 // Check old id without fetching a texture
1013 core::map<std::string, u32>::Node *n;
1014 n = m_name_to_id.find(name);
1015 // If it exists, we will replace the old definition
1018 reuse_old_id = true;
1019 /*infostream<<"TextureSource::buildMainAtlas(): "
1020 <<"Replacing old AtlasPointer"<<std::endl;*/
1023 // Create AtlasPointer
1024 AtlasPointer ap(id);
1025 ap.atlas = NULL; // Set on the second pass
1026 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1027 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1028 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1029 (float)dim.Width/(float)atlas_dim.Height);
1030 ap.tiled = xwise_tiling;
1032 // Create SourceAtlasPointer and add to containers
1033 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1035 m_atlaspointer_cache[id] = nap;
1037 m_atlaspointer_cache.push_back(nap);
1038 m_name_to_id[name] = id;
1040 // Increment position
1041 pos_in_atlas.Y += dim.Height + padding * 2;
1047 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1051 Second pass: set texture pointer in generated AtlasPointers
1053 for(core::map<std::string, bool>::Iterator
1054 i = sourcelist.getIterator();
1055 i.atEnd() == false; i++)
1057 std::string name = i.getNode()->getKey();
1058 if(m_name_to_id.find(name) == NULL)
1060 u32 id = m_name_to_id[name];
1061 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1062 m_atlaspointer_cache[id].a.atlas = t;
1066 Write image to file so that it can be inspected
1068 /*std::string atlaspath = porting::path_user
1069 + DIR_DELIM + "generated_texture_atlas.png";
1070 infostream<<"Removing and writing texture atlas for inspection to "
1071 <<atlaspath<<std::endl;
1072 fs::RecursiveDelete(atlaspath);
1073 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1076 video::IImage* generate_image_from_scratch(std::string name,
1077 IrrlichtDevice *device, SourceImageCache *sourcecache)
1079 /*infostream<<"generate_image_from_scratch(): "
1080 "\""<<name<<"\""<<std::endl;*/
1082 video::IVideoDriver* driver = device->getVideoDriver();
1089 video::IImage *baseimg = NULL;
1091 char separator = '^';
1093 // Find last meta separator in name
1094 s32 last_separator_position = name.find_last_of(separator);
1095 //if(last_separator_position == std::npos)
1096 // last_separator_position = -1;
1098 /*infostream<<"generate_image_from_scratch(): "
1099 <<"last_separator_position="<<last_separator_position
1103 If separator was found, construct the base name and make the
1104 base image using a recursive call
1106 std::string base_image_name;
1107 if(last_separator_position != -1)
1109 // Construct base name
1110 base_image_name = name.substr(0, last_separator_position);
1111 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1112 " to get base image of \""<<name<<"\" = \""
1113 <<base_image_name<<"\""<<std::endl;*/
1114 baseimg = generate_image_from_scratch(base_image_name, device,
1119 Parse out the last part of the name of the image and act
1123 std::string last_part_of_name = name.substr(last_separator_position+1);
1124 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1126 // Generate image according to part of name
1127 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1129 errorstream<<"generate_image_from_scratch(): "
1130 "failed to generate \""<<last_part_of_name<<"\""
1138 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1139 IrrlichtDevice *device, SourceImageCache *sourcecache)
1141 video::IVideoDriver* driver = device->getVideoDriver();
1144 // Stuff starting with [ are special commands
1145 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1147 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1151 if(part_of_name != ""){
1152 errorstream<<"generate_image(): Could not load image \""
1153 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1154 errorstream<<"generate_image(): Creating a dummy"
1155 <<" image for \""<<part_of_name<<"\""<<std::endl;
1158 // Just create a dummy image
1159 //core::dimension2d<u32> dim(2,2);
1160 core::dimension2d<u32> dim(1,1);
1161 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1163 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1164 image->setPixel(1,0, video::SColor(255,0,255,0));
1165 image->setPixel(0,1, video::SColor(255,0,0,255));
1166 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1167 image->setPixel(0,0, video::SColor(255,myrand()%256,
1168 myrand()%256,myrand()%256));
1169 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1170 myrand()%256,myrand()%256));
1171 image->setPixel(0,1, video::SColor(255,myrand()%256,
1172 myrand()%256,myrand()%256));
1173 image->setPixel(1,1, video::SColor(255,myrand()%256,
1174 myrand()%256,myrand()%256));*/
1177 // If base image is NULL, load as base.
1180 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1182 Copy it this way to get an alpha channel.
1183 Otherwise images with alpha cannot be blitted on
1184 images that don't have alpha in the original file.
1186 core::dimension2d<u32> dim = image->getDimension();
1187 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1188 image->copyTo(baseimg);
1191 // Else blit on base.
1194 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1195 // Size of the copied area
1196 core::dimension2d<u32> dim = image->getDimension();
1197 //core::dimension2d<u32> dim(16,16);
1198 // Position to copy the blitted to in the base image
1199 core::position2d<s32> pos_to(0,0);
1200 // Position to copy the blitted from in the blitted image
1201 core::position2d<s32> pos_from(0,0);
1203 /*image->copyToWithAlpha(baseimg, pos_to,
1204 core::rect<s32>(pos_from, dim),
1205 video::SColor(255,255,255,255),
1207 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1214 // A special texture modification
1216 /*infostream<<"generate_image(): generating special "
1217 <<"modification \""<<part_of_name<<"\""
1221 This is the simplest of all; it just adds stuff to the
1222 name so that a separate texture is created.
1224 It is used to make textures for stuff that doesn't want
1225 to implement getting the texture from a bigger texture
1228 if(part_of_name == "[forcesingle")
1230 // If base image is NULL, create a random color
1233 core::dimension2d<u32> dim(1,1);
1234 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1236 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1237 myrand()%256,myrand()%256));
1242 Adds a cracking texture
1244 else if(part_of_name.substr(0,6) == "[crack")
1248 errorstream<<"generate_image(): baseimg==NULL "
1249 <<"for part_of_name=\""<<part_of_name
1250 <<"\", cancelling."<<std::endl;
1254 // Crack image number and overlay option
1255 s32 progression = 0;
1256 bool use_overlay = false;
1257 if(part_of_name.substr(6,1) == "o")
1259 progression = stoi(part_of_name.substr(7));
1264 progression = stoi(part_of_name.substr(6));
1265 use_overlay = false;
1268 // Size of the base image
1269 core::dimension2d<u32> dim_base = baseimg->getDimension();
1274 It is an image with a number of cracking stages
1277 video::IImage *img_crack = sourcecache->getOrLoad(
1278 "crack_anylength.png", device);
1280 if(img_crack && progression >= 0)
1282 // Dimension of original image
1283 core::dimension2d<u32> dim_crack
1284 = img_crack->getDimension();
1285 // Count of crack stages
1286 s32 crack_count = dim_crack.Height / dim_crack.Width;
1287 // Limit progression
1288 if(progression > crack_count-1)
1289 progression = crack_count-1;
1290 // Dimension of a single crack stage
1291 core::dimension2d<u32> dim_crack_cropped(
1295 // Create cropped and scaled crack images
1296 video::IImage *img_crack_cropped = driver->createImage(
1297 video::ECF_A8R8G8B8, dim_crack_cropped);
1298 video::IImage *img_crack_scaled = driver->createImage(
1299 video::ECF_A8R8G8B8, dim_base);
1301 if(img_crack_cropped && img_crack_scaled)
1304 v2s32 pos_crack(0, progression*dim_crack.Width);
1305 img_crack->copyTo(img_crack_cropped,
1307 core::rect<s32>(pos_crack, dim_crack_cropped));
1308 // Scale crack image by copying
1309 img_crack_cropped->copyToScaling(img_crack_scaled);
1310 // Copy or overlay crack image
1313 overlay(baseimg, img_crack_scaled);
1317 /*img_crack_scaled->copyToWithAlpha(
1320 core::rect<s32>(v2s32(0,0), dim_base),
1321 video::SColor(255,255,255,255));*/
1322 blit_with_alpha(img_crack_scaled, baseimg,
1323 v2s32(0,0), v2s32(0,0), dim_base);
1327 if(img_crack_scaled)
1328 img_crack_scaled->drop();
1330 if(img_crack_cropped)
1331 img_crack_cropped->drop();
1337 [combine:WxH:X,Y=filename:X,Y=filename2
1338 Creates a bigger texture from an amount of smaller ones
1340 else if(part_of_name.substr(0,8) == "[combine")
1342 Strfnd sf(part_of_name);
1344 u32 w0 = stoi(sf.next("x"));
1345 u32 h0 = stoi(sf.next(":"));
1346 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1347 core::dimension2d<u32> dim(w0,h0);
1350 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1351 baseimg->fill(video::SColor(0,0,0,0));
1353 while(sf.atend() == false)
1355 u32 x = stoi(sf.next(","));
1356 u32 y = stoi(sf.next("="));
1357 std::string filename = sf.next(":");
1358 infostream<<"Adding \""<<filename
1359 <<"\" to combined ("<<x<<","<<y<<")"
1361 video::IImage *img = sourcecache->getOrLoad(filename, device);
1364 core::dimension2d<u32> dim = img->getDimension();
1365 infostream<<"Size "<<dim.Width
1366 <<"x"<<dim.Height<<std::endl;
1367 core::position2d<s32> pos_base(x, y);
1368 video::IImage *img2 =
1369 driver->createImage(video::ECF_A8R8G8B8, dim);
1372 /*img2->copyToWithAlpha(baseimg, pos_base,
1373 core::rect<s32>(v2s32(0,0), dim),
1374 video::SColor(255,255,255,255),
1376 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1381 infostream<<"img==NULL"<<std::endl;
1388 else if(part_of_name.substr(0,9) == "[brighten")
1392 errorstream<<"generate_image(): baseimg==NULL "
1393 <<"for part_of_name=\""<<part_of_name
1394 <<"\", cancelling."<<std::endl;
1402 Make image completely opaque.
1403 Used for the leaves texture when in old leaves mode, so
1404 that the transparent parts don't look completely black
1405 when simple alpha channel is used for rendering.
1407 else if(part_of_name.substr(0,8) == "[noalpha")
1411 errorstream<<"generate_image(): baseimg==NULL "
1412 <<"for part_of_name=\""<<part_of_name
1413 <<"\", cancelling."<<std::endl;
1417 core::dimension2d<u32> dim = baseimg->getDimension();
1419 // Set alpha to full
1420 for(u32 y=0; y<dim.Height; y++)
1421 for(u32 x=0; x<dim.Width; x++)
1423 video::SColor c = baseimg->getPixel(x,y);
1425 baseimg->setPixel(x,y,c);
1430 Convert one color to transparent.
1432 else if(part_of_name.substr(0,11) == "[makealpha:")
1436 errorstream<<"generate_image(): baseimg==NULL "
1437 <<"for part_of_name=\""<<part_of_name
1438 <<"\", cancelling."<<std::endl;
1442 Strfnd sf(part_of_name.substr(11));
1443 u32 r1 = stoi(sf.next(","));
1444 u32 g1 = stoi(sf.next(","));
1445 u32 b1 = stoi(sf.next(""));
1446 std::string filename = sf.next("");
1448 core::dimension2d<u32> dim = baseimg->getDimension();
1450 /*video::IImage *oldbaseimg = baseimg;
1451 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1452 oldbaseimg->copyTo(baseimg);
1453 oldbaseimg->drop();*/
1455 // Set alpha to full
1456 for(u32 y=0; y<dim.Height; y++)
1457 for(u32 x=0; x<dim.Width; x++)
1459 video::SColor c = baseimg->getPixel(x,y);
1461 u32 g = c.getGreen();
1462 u32 b = c.getBlue();
1463 if(!(r == r1 && g == g1 && b == b1))
1466 baseimg->setPixel(x,y,c);
1471 Rotates and/or flips the image.
1473 N can be a number (between 0 and 7) or a transform name.
1474 Rotations are counter-clockwise.
1476 1 R90 rotate by 90 degrees
1477 2 R180 rotate by 180 degrees
1478 3 R270 rotate by 270 degrees
1480 5 FXR90 flip X then rotate by 90 degrees
1482 7 FYR90 flip Y then rotate by 90 degrees
1484 Note: Transform names can be concatenated to produce
1485 their product (applies the first then the second).
1486 The resulting transform will be equivalent to one of the
1487 eight existing ones, though (see: dihedral group).
1489 else if(part_of_name.substr(0,10) == "[transform")
1493 errorstream<<"generate_image(): baseimg==NULL "
1494 <<"for part_of_name=\""<<part_of_name
1495 <<"\", cancelling."<<std::endl;
1499 u32 transform = parseImageTransform(part_of_name.substr(10));
1500 core::dimension2d<u32> dim = imageTransformDimension(
1501 transform, baseimg->getDimension());
1502 video::IImage *image = driver->createImage(
1503 baseimg->getColorFormat(), dim);
1505 imageTransform(transform, baseimg, image);
1510 [inventorycube{topimage{leftimage{rightimage
1511 In every subimage, replace ^ with &.
1512 Create an "inventory cube".
1513 NOTE: This should be used only on its own.
1514 Example (a grass block (not actually used in game):
1515 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1517 else if(part_of_name.substr(0,14) == "[inventorycube")
1521 errorstream<<"generate_image(): baseimg!=NULL "
1522 <<"for part_of_name=\""<<part_of_name
1523 <<"\", cancelling."<<std::endl;
1527 str_replace_char(part_of_name, '&', '^');
1528 Strfnd sf(part_of_name);
1530 std::string imagename_top = sf.next("{");
1531 std::string imagename_left = sf.next("{");
1532 std::string imagename_right = sf.next("{");
1534 // Generate images for the faces of the cube
1535 video::IImage *img_top = generate_image_from_scratch(
1536 imagename_top, device, sourcecache);
1537 video::IImage *img_left = generate_image_from_scratch(
1538 imagename_left, device, sourcecache);
1539 video::IImage *img_right = generate_image_from_scratch(
1540 imagename_right, device, sourcecache);
1541 assert(img_top && img_left && img_right);
1543 // Create textures from images
1544 video::ITexture *texture_top = driver->addTexture(
1545 (imagename_top + "__temp__").c_str(), img_top);
1546 video::ITexture *texture_left = driver->addTexture(
1547 (imagename_left + "__temp__").c_str(), img_left);
1548 video::ITexture *texture_right = driver->addTexture(
1549 (imagename_right + "__temp__").c_str(), img_right);
1550 assert(texture_top && texture_left && texture_right);
1558 Draw a cube mesh into a render target texture
1560 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1561 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1562 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1563 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1564 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1565 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1566 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1567 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1569 core::dimension2d<u32> dim(64,64);
1570 std::string rtt_texture_name = part_of_name + "_RTT";
1572 v3f camera_position(0, 1.0, -1.5);
1573 camera_position.rotateXZBy(45);
1574 v3f camera_lookat(0, 0, 0);
1575 core::CMatrix4<f32> camera_projection_matrix;
1576 // Set orthogonal projection
1577 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1578 1.65, 1.65, 0, 100);
1580 video::SColorf ambient_light(0.2,0.2,0.2);
1581 v3f light_position(10, 100, -50);
1582 video::SColorf light_color(0.5,0.5,0.5);
1583 f32 light_radius = 1000;
1585 video::ITexture *rtt = generateTextureFromMesh(
1586 cube, device, dim, rtt_texture_name,
1589 camera_projection_matrix,
1598 // Free textures of images
1599 driver->removeTexture(texture_top);
1600 driver->removeTexture(texture_left);
1601 driver->removeTexture(texture_right);
1605 baseimg = generate_image_from_scratch(
1606 imagename_top, device, sourcecache);
1610 // Create image of render target
1611 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1614 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1618 image->copyTo(baseimg);
1623 [lowpart:percent:filename
1624 Adds the lower part of a texture
1626 else if(part_of_name.substr(0,9) == "[lowpart:")
1628 Strfnd sf(part_of_name);
1630 u32 percent = stoi(sf.next(":"));
1631 std::string filename = sf.next(":");
1632 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1635 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1636 video::IImage *img = sourcecache->getOrLoad(filename, device);
1639 core::dimension2d<u32> dim = img->getDimension();
1640 core::position2d<s32> pos_base(0, 0);
1641 video::IImage *img2 =
1642 driver->createImage(video::ECF_A8R8G8B8, dim);
1645 core::position2d<s32> clippos(0, 0);
1646 clippos.Y = dim.Height * (100-percent) / 100;
1647 core::dimension2d<u32> clipdim = dim;
1648 clipdim.Height = clipdim.Height * percent / 100 + 1;
1649 core::rect<s32> cliprect(clippos, clipdim);
1650 img2->copyToWithAlpha(baseimg, pos_base,
1651 core::rect<s32>(v2s32(0,0), dim),
1652 video::SColor(255,255,255,255),
1659 Crops a frame of a vertical animation.
1660 N = frame count, I = frame index
1662 else if(part_of_name.substr(0,15) == "[verticalframe:")
1664 Strfnd sf(part_of_name);
1666 u32 frame_count = stoi(sf.next(":"));
1667 u32 frame_index = stoi(sf.next(":"));
1669 if(baseimg == NULL){
1670 errorstream<<"generate_image(): baseimg!=NULL "
1671 <<"for part_of_name=\""<<part_of_name
1672 <<"\", cancelling."<<std::endl;
1676 v2u32 frame_size = baseimg->getDimension();
1677 frame_size.Y /= frame_count;
1679 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1682 errorstream<<"generate_image(): Could not create image "
1683 <<"for part_of_name=\""<<part_of_name
1684 <<"\", cancelling."<<std::endl;
1688 // Fill target image with transparency
1689 img->fill(video::SColor(0,0,0,0));
1691 core::dimension2d<u32> dim = frame_size;
1692 core::position2d<s32> pos_dst(0, 0);
1693 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1694 baseimg->copyToWithAlpha(img, pos_dst,
1695 core::rect<s32>(pos_src, dim),
1696 video::SColor(255,255,255,255),
1704 errorstream<<"generate_image(): Invalid "
1705 " modification: \""<<part_of_name<<"\""<<std::endl;
1712 void overlay(video::IImage *image, video::IImage *overlay)
1715 Copy overlay to image, taking alpha into account.
1716 Where image is transparent, don't copy from overlay.
1717 Images sizes must be identical.
1719 if(image == NULL || overlay == NULL)
1722 core::dimension2d<u32> dim = image->getDimension();
1723 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1724 assert(dim == dim_overlay);
1726 for(u32 y=0; y<dim.Height; y++)
1727 for(u32 x=0; x<dim.Width; x++)
1729 video::SColor c1 = image->getPixel(x,y);
1730 video::SColor c2 = overlay->getPixel(x,y);
1731 u32 a1 = c1.getAlpha();
1732 u32 a2 = c2.getAlpha();
1733 if(a1 == 255 && a2 != 0)
1735 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1736 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1737 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1739 image->setPixel(x,y,c1);
1744 Draw an image on top of an another one, using the alpha channel of the
1747 This exists because IImage::copyToWithAlpha() doesn't seem to always
1750 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1751 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1753 for(u32 y0=0; y0<size.Y; y0++)
1754 for(u32 x0=0; x0<size.X; x0++)
1756 s32 src_x = src_pos.X + x0;
1757 s32 src_y = src_pos.Y + y0;
1758 s32 dst_x = dst_pos.X + x0;
1759 s32 dst_y = dst_pos.Y + y0;
1760 video::SColor src_c = src->getPixel(src_x, src_y);
1761 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1762 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1763 dst->setPixel(dst_x, dst_y, dst_c);
1767 void brighten(video::IImage *image)
1772 core::dimension2d<u32> dim = image->getDimension();
1774 for(u32 y=0; y<dim.Height; y++)
1775 for(u32 x=0; x<dim.Width; x++)
1777 video::SColor c = image->getPixel(x,y);
1778 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1779 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1780 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1781 image->setPixel(x,y,c);
1785 u32 parseImageTransform(const std::string& s)
1787 int total_transform = 0;
1789 std::string transform_names[8];
1790 transform_names[0] = "i";
1791 transform_names[1] = "r90";
1792 transform_names[2] = "r180";
1793 transform_names[3] = "r270";
1794 transform_names[4] = "fx";
1795 transform_names[6] = "fy";
1797 std::size_t pos = 0;
1798 while(pos < s.size())
1801 for(int i = 0; i <= 7; ++i)
1803 const std::string &name_i = transform_names[i];
1805 if(s[pos] == ('0' + i))
1811 else if(!(name_i.empty()) &&
1812 lowercase(s.substr(pos, name_i.size())) == name_i)
1815 pos += name_i.size();
1822 // Multiply total_transform and transform in the group D4
1825 new_total = (transform + total_transform) % 4;
1827 new_total = (transform - total_transform + 8) % 4;
1828 if((transform >= 4) ^ (total_transform >= 4))
1831 total_transform = new_total;
1833 return total_transform;
1836 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1838 if(transform % 2 == 0)
1841 return core::dimension2d<u32>(dim.Height, dim.Width);
1844 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1846 if(src == NULL || dst == NULL)
1849 core::dimension2d<u32> srcdim = src->getDimension();
1850 core::dimension2d<u32> dstdim = dst->getDimension();
1852 assert(dstdim == imageTransformDimension(transform, srcdim));
1853 assert(transform >= 0 && transform <= 7);
1856 Compute the transformation from source coordinates (sx,sy)
1857 to destination coordinates (dx,dy).
1861 if(transform == 0) // identity
1862 sxn = 0, syn = 2; // sx = dx, sy = dy
1863 else if(transform == 1) // rotate by 90 degrees ccw
1864 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1865 else if(transform == 2) // rotate by 180 degrees
1866 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1867 else if(transform == 3) // rotate by 270 degrees ccw
1868 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1869 else if(transform == 4) // flip x
1870 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1871 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1872 sxn = 2, syn = 0; // sx = dy, sy = dx
1873 else if(transform == 6) // flip y
1874 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1875 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1876 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1878 for(u32 dy=0; dy<dstdim.Height; dy++)
1879 for(u32 dx=0; dx<dstdim.Width; dx++)
1881 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1882 u32 sx = entries[sxn];
1883 u32 sy = entries[syn];
1884 video::SColor c = src->getPixel(sx,sy);
1885 dst->setPixel(dx,dy,c);