3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "main.h" // for g_settings
26 #include <ICameraSceneNode.h>
28 #include "mapnode.h" // For texture atlas making
29 #include "nodedef.h" // For texture atlas making
31 #include "util/string.h"
32 #include "util/container.h"
33 #include "util/thread.h"
34 #include "util/numeric.h"
37 A cache from texture name to texture path
39 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
42 Replaces the filename extension.
44 std::string image = "a/image.png"
45 replace_ext(image, "jpg")
46 -> image = "a/image.jpg"
47 Returns true on success.
49 static bool replace_ext(std::string &path, const char *ext)
53 // Find place of last dot, fail if \ or / found.
55 for(s32 i=path.size()-1; i>=0; i--)
63 if(path[i] == '\\' || path[i] == '/')
66 // If not found, return an empty string
69 // Else make the new path
70 path = path.substr(0, last_dot_i+1) + ext;
75 Find out the full path of an image by trying different filename
80 static std::string getImagePath(std::string path)
82 // A NULL-ended list of possible image extensions
83 const char *extensions[] = {
84 "png", "jpg", "bmp", "tga",
85 "pcx", "ppm", "psd", "wal", "rgb",
88 // If there is no extension, add one
89 if(removeStringEnd(path, extensions) == "")
91 // Check paths until something is found to exist
92 const char **ext = extensions;
94 bool r = replace_ext(path, *ext);
97 if(fs::PathExists(path))
100 while((++ext) != NULL);
106 Gets the path to a texture by first checking if the texture exists
107 in texture_path and if not, using the data path.
109 Checks all supported extensions by replacing the original extension.
111 If not found, returns "".
113 Utilizes a thread-safe cache.
115 std::string getTexturePath(const std::string &filename)
117 std::string fullpath = "";
121 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
126 Check from texture_path
128 std::string texture_path = g_settings->get("texture_path");
129 if(texture_path != "")
131 std::string testpath = texture_path + DIR_DELIM + filename;
132 // Check all filename extensions. Returns "" if not found.
133 fullpath = getImagePath(testpath);
137 Check from $user/textures/all
141 std::string texture_path = porting::path_user + DIR_DELIM
142 + "textures" + DIR_DELIM + "all";
143 std::string testpath = texture_path + DIR_DELIM + filename;
144 // Check all filename extensions. Returns "" if not found.
145 fullpath = getImagePath(testpath);
149 Check from default data directory
153 std::string base_path = porting::path_share + DIR_DELIM + "textures"
154 + DIR_DELIM + "base" + DIR_DELIM + "pack";
155 std::string testpath = base_path + DIR_DELIM + filename;
156 // Check all filename extensions. Returns "" if not found.
157 fullpath = getImagePath(testpath);
160 // Add to cache (also an empty result is cached)
161 g_texturename_to_path_cache.set(filename, fullpath);
168 An internal variant of AtlasPointer with more data.
169 (well, more like a wrapper)
172 struct SourceAtlasPointer
176 video::IImage *atlas_img; // The source image of the atlas
177 // Integer variants of position and size
182 const std::string &name_,
183 AtlasPointer a_=AtlasPointer(0, NULL),
184 video::IImage *atlas_img_=NULL,
185 v2s32 intpos_=v2s32(0,0),
186 v2u32 intsize_=v2u32(0,0)
190 atlas_img(atlas_img_),
198 SourceImageCache: A cache used for storing source images.
201 class SourceImageCache
204 void insert(const std::string &name, video::IImage *img,
205 bool prefer_local, video::IVideoDriver *driver)
209 std::map<std::string, video::IImage*>::iterator n;
210 n = m_images.find(name);
211 if(n != m_images.end()){
212 video::IImage *oldimg = n->second;
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 std::map<std::string, video::IImage*>::iterator n;
233 n = m_images.find(name);
234 if(n != m_images.end())
238 // Primarily fetches from cache, secondarily tries to read from filesystem
239 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
241 std::map<std::string, video::IImage*>::iterator n;
242 n = m_images.find(name);
243 if(n != m_images.end()){
244 n->second->grab(); // Grab for caller
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 std::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 bool isKnownSourceImage(const std::string &name)
378 bool is_known = false;
379 bool cache_found = m_source_image_existence.get(name, &is_known);
382 // Not found in cache; find out if a local file exists
383 is_known = (getTexturePath(name) != "");
384 m_source_image_existence.set(name, is_known);
388 // Processes queued texture requests from other threads.
389 // Shall be called from the main thread.
392 // Insert an image into the cache without touching the filesystem.
393 // Shall be called from the main thread.
394 void insertSourceImage(const std::string &name, video::IImage *img);
396 // Rebuild images and textures from the current set of source images
397 // Shall be called from the main thread.
398 void rebuildImagesAndTextures();
400 // Build the main texture atlas which contains most of the
402 void buildMainAtlas(class IGameDef *gamedef);
406 // The id of the thread that is allowed to use irrlicht directly
407 threadid_t m_main_thread;
408 // The irrlicht device
409 IrrlichtDevice *m_device;
411 // Cache of source images
412 // This should be only accessed from the main thread
413 SourceImageCache m_sourcecache;
415 // Thread-safe cache of what source images are known (true = known)
416 MutexedMap<std::string, bool> m_source_image_existence;
418 // A texture id is index in this array.
419 // The first position contains a NULL texture.
420 std::vector<SourceAtlasPointer> m_atlaspointer_cache;
421 // Maps a texture name to an index in the former.
422 std::map<std::string, u32> m_name_to_id;
423 // The two former containers are behind this mutex
424 JMutex m_atlaspointer_cache_mutex;
426 // Main texture atlas. This is filled at startup and is then not touched.
427 video::IImage *m_main_atlas_image;
428 video::ITexture *m_main_atlas_texture;
430 // Queued texture fetches (to be processed by the main thread)
431 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
434 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
436 return new TextureSource(device);
439 TextureSource::TextureSource(IrrlichtDevice *device):
441 m_main_atlas_image(NULL),
442 m_main_atlas_texture(NULL)
446 m_atlaspointer_cache_mutex.Init();
448 m_main_thread = get_current_thread_id();
450 // Add a NULL AtlasPointer as the first index, named ""
451 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
452 m_name_to_id[""] = 0;
455 TextureSource::~TextureSource()
459 u32 TextureSource::getTextureId(const std::string &name)
461 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
465 See if texture already exists
467 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
468 std::map<std::string, u32>::iterator n;
469 n = m_name_to_id.find(name);
470 if(n != m_name_to_id.end())
479 if(get_current_thread_id() == m_main_thread)
481 return getTextureIdDirect(name);
485 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
487 // We're gonna ask the result to be put into here
488 ResultQueue<std::string, u32, u8, u8> result_queue;
490 // Throw a request in
491 m_get_texture_queue.add(name, 0, 0, &result_queue);
493 infostream<<"Waiting for texture from main thread, name=\""
494 <<name<<"\""<<std::endl;
498 // Wait result for a second
499 GetResult<std::string, u32, u8, u8>
500 result = result_queue.pop_front(1000);
502 // Check that at least something worked OK
503 assert(result.key == name);
507 catch(ItemNotFoundException &e)
509 infostream<<"Waiting for texture timed out."<<std::endl;
514 infostream<<"getTextureId(): Failed"<<std::endl;
519 // Overlay image on top of another image (used for cracks)
520 void overlay(video::IImage *image, video::IImage *overlay);
522 // Draw an image on top of an another one, using the alpha channel of the
524 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
525 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
528 void brighten(video::IImage *image);
529 // Parse a transform name
530 u32 parseImageTransform(const std::string& s);
531 // Apply transform to image dimension
532 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
533 // Apply transform to image data
534 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
537 Generate image based on a string like "stone.png" or "[crack0".
538 if baseimg is NULL, it is created. Otherwise stuff is made on it.
540 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
541 IrrlichtDevice *device, SourceImageCache *sourcecache);
544 Generates an image from a full string like
545 "stone.png^mineral_coal.png^[crack0".
547 This is used by buildMainAtlas().
549 video::IImage* generate_image_from_scratch(std::string name,
550 IrrlichtDevice *device, SourceImageCache *sourcecache);
553 This method generates all the textures
555 u32 TextureSource::getTextureIdDirect(const std::string &name)
557 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
559 // Empty name means texture 0
562 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
567 Calling only allowed from main thread
569 if(get_current_thread_id() != m_main_thread)
571 errorstream<<"TextureSource::getTextureIdDirect() "
572 "called not from main thread"<<std::endl;
577 See if texture already exists
580 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
582 std::map<std::string, u32>::iterator n;
583 n = m_name_to_id.find(name);
584 if(n != m_name_to_id.end())
586 /*infostream<<"getTextureIdDirect(): \""<<name
587 <<"\" found in cache"<<std::endl;*/
592 /*infostream<<"getTextureIdDirect(): \""<<name
593 <<"\" NOT found in cache. Creating it."<<std::endl;*/
599 char separator = '^';
602 This is set to the id of the base image.
603 If left 0, there is no base image and a completely new image
606 u32 base_image_id = 0;
608 // Find last meta separator in name
609 s32 last_separator_position = -1;
610 for(s32 i=name.size()-1; i>=0; i--)
612 if(name[i] == separator)
614 last_separator_position = i;
619 If separator was found, construct the base name and make the
620 base image using a recursive call
622 std::string base_image_name;
623 if(last_separator_position != -1)
625 // Construct base name
626 base_image_name = name.substr(0, last_separator_position);
627 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
628 " to get base image of \""<<name<<"\" = \""
629 <<base_image_name<<"\""<<std::endl;*/
630 base_image_id = getTextureIdDirect(base_image_name);
633 //infostream<<"base_image_id="<<base_image_id<<std::endl;
635 video::IVideoDriver* driver = m_device->getVideoDriver();
638 video::ITexture *t = NULL;
641 An image will be built from files and then converted into a texture.
643 video::IImage *baseimg = NULL;
645 // If a base image was found, copy it to baseimg
646 if(base_image_id != 0)
648 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
650 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
652 video::IImage *image = ap.atlas_img;
656 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
657 <<"cache: \""<<base_image_name<<"\""
662 core::dimension2d<u32> dim = ap.intsize;
664 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
666 core::position2d<s32> pos_to(0,0);
667 core::position2d<s32> pos_from = ap.intpos;
671 v2s32(0,0), // position in target
672 core::rect<s32>(pos_from, dim) // from
675 /*infostream<<"getTextureIdDirect(): Loaded \""
676 <<base_image_name<<"\" from image cache"
682 Parse out the last part of the name of the image and act
686 std::string last_part_of_name = name.substr(last_separator_position+1);
687 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
689 // Generate image according to part of name
690 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
692 errorstream<<"getTextureIdDirect(): "
693 "failed to generate \""<<last_part_of_name<<"\""
697 // If no resulting image, print a warning
700 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
701 " create texture \""<<name<<"\""<<std::endl;
706 // Create texture from resulting image
707 t = driver->addTexture(name.c_str(), baseimg);
711 Add texture to caches (add NULL textures too)
714 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
716 u32 id = m_atlaspointer_cache.size();
722 core::dimension2d<u32> baseimg_dim(0,0);
724 baseimg_dim = baseimg->getDimension();
725 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
726 m_atlaspointer_cache.push_back(nap);
727 m_name_to_id[name] = id;
729 /*infostream<<"getTextureIdDirect(): "
730 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
735 std::string TextureSource::getTextureName(u32 id)
737 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
739 if(id >= m_atlaspointer_cache.size())
741 errorstream<<"TextureSource::getTextureName(): id="<<id
742 <<" >= m_atlaspointer_cache.size()="
743 <<m_atlaspointer_cache.size()<<std::endl;
747 return m_atlaspointer_cache[id].name;
751 AtlasPointer TextureSource::getTexture(u32 id)
753 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
755 if(id >= m_atlaspointer_cache.size())
756 return AtlasPointer(0, NULL);
758 return m_atlaspointer_cache[id].a;
761 void TextureSource::updateAP(AtlasPointer &ap)
763 AtlasPointer ap2 = getTexture(ap.id);
767 void TextureSource::processQueue()
772 if(!m_get_texture_queue.empty())
774 GetRequest<std::string, u32, u8, u8>
775 request = m_get_texture_queue.pop();
777 /*infostream<<"TextureSource::processQueue(): "
778 <<"got texture request with "
779 <<"name=\""<<request.key<<"\""
782 GetResult<std::string, u32, u8, u8>
784 result.key = request.key;
785 result.callers = request.callers;
786 result.item = getTextureIdDirect(request.key);
788 request.dest->push_back(result);
792 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
794 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
796 assert(get_current_thread_id() == m_main_thread);
798 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
799 m_source_image_existence.set(name, true);
802 void TextureSource::rebuildImagesAndTextures()
804 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
806 /*// Oh well... just clear everything, they'll load sometime.
807 m_atlaspointer_cache.clear();
808 m_name_to_id.clear();*/
810 video::IVideoDriver* driver = m_device->getVideoDriver();
812 // Remove source images from textures to disable inheriting textures
813 // from existing textures
814 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
815 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
816 sap->atlas_img->drop();
817 sap->atlas_img = NULL;
821 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
822 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
824 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
825 // Create texture from resulting image
826 video::ITexture *t = NULL;
828 t = driver->addTexture(sap->name.c_str(), img);
832 sap->a.pos = v2f(0,0);
833 sap->a.size = v2f(1,1);
835 sap->atlas_img = img;
836 sap->intpos = v2s32(0,0);
837 sap->intsize = img->getDimension();
841 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
843 assert(gamedef->tsrc() == this);
844 INodeDefManager *ndef = gamedef->ndef();
846 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
848 //return; // Disable (for testing)
850 video::IVideoDriver* driver = m_device->getVideoDriver();
853 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
855 // Create an image of the right size
856 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
857 core::dimension2d<u32> atlas_dim(2048,2048);
858 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
859 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
860 video::IImage *atlas_img =
861 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
863 if(atlas_img == NULL)
865 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
866 "image; not building texture atlas."<<std::endl;
871 Grab list of stuff to include in the texture atlas from the
872 main content features
875 std::set<std::string> sourcelist;
877 for(u16 j=0; j<MAX_CONTENT+1; j++)
879 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
881 const ContentFeatures &f = ndef->get(j);
882 for(u32 i=0; i<6; i++)
884 std::string name = f.tiledef[i].name;
885 sourcelist.insert(name);
889 infostream<<"Creating texture atlas out of textures: ";
890 for(std::set<std::string>::iterator
891 i = sourcelist.begin();
892 i != sourcelist.end(); ++i)
894 std::string name = *i;
895 infostream<<"\""<<name<<"\" ";
897 infostream<<std::endl;
899 // Padding to disallow texture bleeding
900 // (16 needed if mipmapping is used; otherwise less will work too)
902 s32 column_padding = 16;
903 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
906 First pass: generate almost everything
908 core::position2d<s32> pos_in_atlas(0,0);
910 pos_in_atlas.X = column_padding;
911 pos_in_atlas.Y = padding;
913 for(std::set<std::string>::iterator
914 i = sourcelist.begin();
915 i != sourcelist.end(); ++i)
917 std::string name = *i;
919 // Generate image by name
920 video::IImage *img2 = generate_image_from_scratch(name, m_device,
924 errorstream<<"TextureSource::buildMainAtlas(): "
925 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
929 core::dimension2d<u32> dim = img2->getDimension();
931 // Don't add to atlas if image is too large
932 core::dimension2d<u32> max_size_in_atlas(64,64);
933 if(dim.Width > max_size_in_atlas.Width
934 || dim.Height > max_size_in_atlas.Height)
936 infostream<<"TextureSource::buildMainAtlas(): Not adding "
937 <<"\""<<name<<"\" because image is large"<<std::endl;
941 // Wrap columns and stop making atlas if atlas is full
942 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
944 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
945 errorstream<<"TextureSource::buildMainAtlas(): "
946 <<"Atlas is full, not adding more textures."
950 pos_in_atlas.Y = padding;
951 pos_in_atlas.X += column_width + column_padding*2;
954 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
955 <<"\" to texture atlas"<<std::endl;*/
957 // Tile it a few times in the X direction
958 u16 xwise_tiling = column_width / dim.Width;
959 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
961 for(u32 j=0; j<xwise_tiling; j++)
963 // Copy the copy to the atlas
964 /*img2->copyToWithAlpha(atlas_img,
965 pos_in_atlas + v2s32(j*dim.Width,0),
966 core::rect<s32>(v2s32(0,0), dim),
967 video::SColor(255,255,255,255),
969 img2->copyTo(atlas_img,
970 pos_in_atlas + v2s32(j*dim.Width,0),
971 core::rect<s32>(v2s32(0,0), dim),
975 // Copy the borders a few times to disallow texture bleeding
976 for(u32 side=0; side<2; side++) // top and bottom
977 for(s32 y0=0; y0<padding; y0++)
978 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
984 dst_y = y0 + pos_in_atlas.Y + dim.Height;
985 src_y = pos_in_atlas.Y + dim.Height - 1;
989 dst_y = -y0 + pos_in_atlas.Y-1;
990 src_y = pos_in_atlas.Y;
992 s32 x = x0 + pos_in_atlas.X;
993 video::SColor c = atlas_img->getPixel(x, src_y);
994 atlas_img->setPixel(x,dst_y,c);
997 for(u32 side=0; side<2; side++) // left and right
998 for(s32 x0=0; x0<column_padding; x0++)
999 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
1005 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
1006 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1010 dst_x = -x0 + pos_in_atlas.X-1;
1011 src_x = pos_in_atlas.X;
1013 s32 y = y0 + pos_in_atlas.Y;
1014 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1016 video::SColor c = atlas_img->getPixel(src_x, src_y);
1017 atlas_img->setPixel(dst_x,dst_y,c);
1023 Add texture to caches
1026 bool reuse_old_id = false;
1027 u32 id = m_atlaspointer_cache.size();
1028 // Check old id without fetching a texture
1029 std::map<std::string, u32>::iterator n;
1030 n = m_name_to_id.find(name);
1031 // If it exists, we will replace the old definition
1032 if(n != m_name_to_id.end()){
1034 reuse_old_id = true;
1035 /*infostream<<"TextureSource::buildMainAtlas(): "
1036 <<"Replacing old AtlasPointer"<<std::endl;*/
1039 // Create AtlasPointer
1040 AtlasPointer ap(id);
1041 ap.atlas = NULL; // Set on the second pass
1042 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1043 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1044 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1045 (float)dim.Width/(float)atlas_dim.Height);
1046 ap.tiled = xwise_tiling;
1048 // Create SourceAtlasPointer and add to containers
1049 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1051 m_atlaspointer_cache[id] = nap;
1053 m_atlaspointer_cache.push_back(nap);
1054 m_name_to_id[name] = id;
1056 // Increment position
1057 pos_in_atlas.Y += dim.Height + padding * 2;
1063 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1067 Second pass: set texture pointer in generated AtlasPointers
1069 for(std::set<std::string>::iterator
1070 i = sourcelist.begin();
1071 i != sourcelist.end(); ++i)
1073 std::string name = *i;
1074 if(m_name_to_id.find(name) == m_name_to_id.end())
1076 u32 id = m_name_to_id[name];
1077 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1078 m_atlaspointer_cache[id].a.atlas = t;
1082 Write image to file so that it can be inspected
1084 /*std::string atlaspath = porting::path_user
1085 + DIR_DELIM + "generated_texture_atlas.png";
1086 infostream<<"Removing and writing texture atlas for inspection to "
1087 <<atlaspath<<std::endl;
1088 fs::RecursiveDelete(atlaspath);
1089 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1092 video::IImage* generate_image_from_scratch(std::string name,
1093 IrrlichtDevice *device, SourceImageCache *sourcecache)
1095 /*infostream<<"generate_image_from_scratch(): "
1096 "\""<<name<<"\""<<std::endl;*/
1098 video::IVideoDriver* driver = device->getVideoDriver();
1105 video::IImage *baseimg = NULL;
1107 char separator = '^';
1109 // Find last meta separator in name
1110 s32 last_separator_position = name.find_last_of(separator);
1111 //if(last_separator_position == std::npos)
1112 // last_separator_position = -1;
1114 /*infostream<<"generate_image_from_scratch(): "
1115 <<"last_separator_position="<<last_separator_position
1119 If separator was found, construct the base name and make the
1120 base image using a recursive call
1122 std::string base_image_name;
1123 if(last_separator_position != -1)
1125 // Construct base name
1126 base_image_name = name.substr(0, last_separator_position);
1127 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1128 " to get base image of \""<<name<<"\" = \""
1129 <<base_image_name<<"\""<<std::endl;*/
1130 baseimg = generate_image_from_scratch(base_image_name, device,
1135 Parse out the last part of the name of the image and act
1139 std::string last_part_of_name = name.substr(last_separator_position+1);
1140 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1142 // Generate image according to part of name
1143 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1145 errorstream<<"generate_image_from_scratch(): "
1146 "failed to generate \""<<last_part_of_name<<"\""
1154 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1155 IrrlichtDevice *device, SourceImageCache *sourcecache)
1157 video::IVideoDriver* driver = device->getVideoDriver();
1160 // Stuff starting with [ are special commands
1161 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1163 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1167 if(part_of_name != ""){
1168 errorstream<<"generate_image(): Could not load image \""
1169 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1170 errorstream<<"generate_image(): Creating a dummy"
1171 <<" image for \""<<part_of_name<<"\""<<std::endl;
1174 // Just create a dummy image
1175 //core::dimension2d<u32> dim(2,2);
1176 core::dimension2d<u32> dim(1,1);
1177 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1179 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1180 image->setPixel(1,0, video::SColor(255,0,255,0));
1181 image->setPixel(0,1, video::SColor(255,0,0,255));
1182 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1183 image->setPixel(0,0, video::SColor(255,myrand()%256,
1184 myrand()%256,myrand()%256));
1185 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1186 myrand()%256,myrand()%256));
1187 image->setPixel(0,1, video::SColor(255,myrand()%256,
1188 myrand()%256,myrand()%256));
1189 image->setPixel(1,1, video::SColor(255,myrand()%256,
1190 myrand()%256,myrand()%256));*/
1193 // If base image is NULL, load as base.
1196 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1198 Copy it this way to get an alpha channel.
1199 Otherwise images with alpha cannot be blitted on
1200 images that don't have alpha in the original file.
1202 core::dimension2d<u32> dim = image->getDimension();
1203 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1204 image->copyTo(baseimg);
1207 // Else blit on base.
1210 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1211 // Size of the copied area
1212 core::dimension2d<u32> dim = image->getDimension();
1213 //core::dimension2d<u32> dim(16,16);
1214 // Position to copy the blitted to in the base image
1215 core::position2d<s32> pos_to(0,0);
1216 // Position to copy the blitted from in the blitted image
1217 core::position2d<s32> pos_from(0,0);
1219 /*image->copyToWithAlpha(baseimg, pos_to,
1220 core::rect<s32>(pos_from, dim),
1221 video::SColor(255,255,255,255),
1223 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1230 // A special texture modification
1232 /*infostream<<"generate_image(): generating special "
1233 <<"modification \""<<part_of_name<<"\""
1237 This is the simplest of all; it just adds stuff to the
1238 name so that a separate texture is created.
1240 It is used to make textures for stuff that doesn't want
1241 to implement getting the texture from a bigger texture
1244 if(part_of_name == "[forcesingle")
1246 // If base image is NULL, create a random color
1249 core::dimension2d<u32> dim(1,1);
1250 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1252 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1253 myrand()%256,myrand()%256));
1258 Adds a cracking texture
1260 else if(part_of_name.substr(0,6) == "[crack")
1264 errorstream<<"generate_image(): baseimg==NULL "
1265 <<"for part_of_name=\""<<part_of_name
1266 <<"\", cancelling."<<std::endl;
1270 // Crack image number and overlay option
1271 s32 progression = 0;
1272 bool use_overlay = false;
1273 if(part_of_name.substr(6,1) == "o")
1275 progression = stoi(part_of_name.substr(7));
1280 progression = stoi(part_of_name.substr(6));
1281 use_overlay = false;
1284 // Size of the base image
1285 core::dimension2d<u32> dim_base = baseimg->getDimension();
1290 It is an image with a number of cracking stages
1293 video::IImage *img_crack = sourcecache->getOrLoad(
1294 "crack_anylength.png", device);
1296 if(img_crack && progression >= 0)
1298 // Dimension of original image
1299 core::dimension2d<u32> dim_crack
1300 = img_crack->getDimension();
1301 // Count of crack stages
1302 s32 crack_count = dim_crack.Height / dim_crack.Width;
1303 // Limit progression
1304 if(progression > crack_count-1)
1305 progression = crack_count-1;
1306 // Dimension of a single crack stage
1307 core::dimension2d<u32> dim_crack_cropped(
1311 // Create cropped and scaled crack images
1312 video::IImage *img_crack_cropped = driver->createImage(
1313 video::ECF_A8R8G8B8, dim_crack_cropped);
1314 video::IImage *img_crack_scaled = driver->createImage(
1315 video::ECF_A8R8G8B8, dim_base);
1317 if(img_crack_cropped && img_crack_scaled)
1320 v2s32 pos_crack(0, progression*dim_crack.Width);
1321 img_crack->copyTo(img_crack_cropped,
1323 core::rect<s32>(pos_crack, dim_crack_cropped));
1324 // Scale crack image by copying
1325 img_crack_cropped->copyToScaling(img_crack_scaled);
1326 // Copy or overlay crack image
1329 overlay(baseimg, img_crack_scaled);
1333 /*img_crack_scaled->copyToWithAlpha(
1336 core::rect<s32>(v2s32(0,0), dim_base),
1337 video::SColor(255,255,255,255));*/
1338 blit_with_alpha(img_crack_scaled, baseimg,
1339 v2s32(0,0), v2s32(0,0), dim_base);
1343 if(img_crack_scaled)
1344 img_crack_scaled->drop();
1346 if(img_crack_cropped)
1347 img_crack_cropped->drop();
1353 [combine:WxH:X,Y=filename:X,Y=filename2
1354 Creates a bigger texture from an amount of smaller ones
1356 else if(part_of_name.substr(0,8) == "[combine")
1358 Strfnd sf(part_of_name);
1360 u32 w0 = stoi(sf.next("x"));
1361 u32 h0 = stoi(sf.next(":"));
1362 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1363 core::dimension2d<u32> dim(w0,h0);
1366 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1367 baseimg->fill(video::SColor(0,0,0,0));
1369 while(sf.atend() == false)
1371 u32 x = stoi(sf.next(","));
1372 u32 y = stoi(sf.next("="));
1373 std::string filename = sf.next(":");
1374 infostream<<"Adding \""<<filename
1375 <<"\" to combined ("<<x<<","<<y<<")"
1377 video::IImage *img = sourcecache->getOrLoad(filename, device);
1380 core::dimension2d<u32> dim = img->getDimension();
1381 infostream<<"Size "<<dim.Width
1382 <<"x"<<dim.Height<<std::endl;
1383 core::position2d<s32> pos_base(x, y);
1384 video::IImage *img2 =
1385 driver->createImage(video::ECF_A8R8G8B8, dim);
1388 /*img2->copyToWithAlpha(baseimg, pos_base,
1389 core::rect<s32>(v2s32(0,0), dim),
1390 video::SColor(255,255,255,255),
1392 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1397 infostream<<"img==NULL"<<std::endl;
1404 else if(part_of_name.substr(0,9) == "[brighten")
1408 errorstream<<"generate_image(): baseimg==NULL "
1409 <<"for part_of_name=\""<<part_of_name
1410 <<"\", cancelling."<<std::endl;
1418 Make image completely opaque.
1419 Used for the leaves texture when in old leaves mode, so
1420 that the transparent parts don't look completely black
1421 when simple alpha channel is used for rendering.
1423 else if(part_of_name.substr(0,8) == "[noalpha")
1427 errorstream<<"generate_image(): baseimg==NULL "
1428 <<"for part_of_name=\""<<part_of_name
1429 <<"\", cancelling."<<std::endl;
1433 core::dimension2d<u32> dim = baseimg->getDimension();
1435 // Set alpha to full
1436 for(u32 y=0; y<dim.Height; y++)
1437 for(u32 x=0; x<dim.Width; x++)
1439 video::SColor c = baseimg->getPixel(x,y);
1441 baseimg->setPixel(x,y,c);
1446 Convert one color to transparent.
1448 else if(part_of_name.substr(0,11) == "[makealpha:")
1452 errorstream<<"generate_image(): baseimg==NULL "
1453 <<"for part_of_name=\""<<part_of_name
1454 <<"\", cancelling."<<std::endl;
1458 Strfnd sf(part_of_name.substr(11));
1459 u32 r1 = stoi(sf.next(","));
1460 u32 g1 = stoi(sf.next(","));
1461 u32 b1 = stoi(sf.next(""));
1462 std::string filename = sf.next("");
1464 core::dimension2d<u32> dim = baseimg->getDimension();
1466 /*video::IImage *oldbaseimg = baseimg;
1467 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1468 oldbaseimg->copyTo(baseimg);
1469 oldbaseimg->drop();*/
1471 // Set alpha to full
1472 for(u32 y=0; y<dim.Height; y++)
1473 for(u32 x=0; x<dim.Width; x++)
1475 video::SColor c = baseimg->getPixel(x,y);
1477 u32 g = c.getGreen();
1478 u32 b = c.getBlue();
1479 if(!(r == r1 && g == g1 && b == b1))
1482 baseimg->setPixel(x,y,c);
1487 Rotates and/or flips the image.
1489 N can be a number (between 0 and 7) or a transform name.
1490 Rotations are counter-clockwise.
1492 1 R90 rotate by 90 degrees
1493 2 R180 rotate by 180 degrees
1494 3 R270 rotate by 270 degrees
1496 5 FXR90 flip X then rotate by 90 degrees
1498 7 FYR90 flip Y then rotate by 90 degrees
1500 Note: Transform names can be concatenated to produce
1501 their product (applies the first then the second).
1502 The resulting transform will be equivalent to one of the
1503 eight existing ones, though (see: dihedral group).
1505 else if(part_of_name.substr(0,10) == "[transform")
1509 errorstream<<"generate_image(): baseimg==NULL "
1510 <<"for part_of_name=\""<<part_of_name
1511 <<"\", cancelling."<<std::endl;
1515 u32 transform = parseImageTransform(part_of_name.substr(10));
1516 core::dimension2d<u32> dim = imageTransformDimension(
1517 transform, baseimg->getDimension());
1518 video::IImage *image = driver->createImage(
1519 baseimg->getColorFormat(), dim);
1521 imageTransform(transform, baseimg, image);
1526 [inventorycube{topimage{leftimage{rightimage
1527 In every subimage, replace ^ with &.
1528 Create an "inventory cube".
1529 NOTE: This should be used only on its own.
1530 Example (a grass block (not actually used in game):
1531 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1533 else if(part_of_name.substr(0,14) == "[inventorycube")
1537 errorstream<<"generate_image(): baseimg!=NULL "
1538 <<"for part_of_name=\""<<part_of_name
1539 <<"\", cancelling."<<std::endl;
1543 str_replace_char(part_of_name, '&', '^');
1544 Strfnd sf(part_of_name);
1546 std::string imagename_top = sf.next("{");
1547 std::string imagename_left = sf.next("{");
1548 std::string imagename_right = sf.next("{");
1550 // Generate images for the faces of the cube
1551 video::IImage *img_top = generate_image_from_scratch(
1552 imagename_top, device, sourcecache);
1553 video::IImage *img_left = generate_image_from_scratch(
1554 imagename_left, device, sourcecache);
1555 video::IImage *img_right = generate_image_from_scratch(
1556 imagename_right, device, sourcecache);
1557 assert(img_top && img_left && img_right);
1559 // Create textures from images
1560 video::ITexture *texture_top = driver->addTexture(
1561 (imagename_top + "__temp__").c_str(), img_top);
1562 video::ITexture *texture_left = driver->addTexture(
1563 (imagename_left + "__temp__").c_str(), img_left);
1564 video::ITexture *texture_right = driver->addTexture(
1565 (imagename_right + "__temp__").c_str(), img_right);
1566 assert(texture_top && texture_left && texture_right);
1574 Draw a cube mesh into a render target texture
1576 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1577 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1578 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1579 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1580 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1581 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1582 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1583 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1585 core::dimension2d<u32> dim(64,64);
1586 std::string rtt_texture_name = part_of_name + "_RTT";
1588 v3f camera_position(0, 1.0, -1.5);
1589 camera_position.rotateXZBy(45);
1590 v3f camera_lookat(0, 0, 0);
1591 core::CMatrix4<f32> camera_projection_matrix;
1592 // Set orthogonal projection
1593 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1594 1.65, 1.65, 0, 100);
1596 video::SColorf ambient_light(0.2,0.2,0.2);
1597 v3f light_position(10, 100, -50);
1598 video::SColorf light_color(0.5,0.5,0.5);
1599 f32 light_radius = 1000;
1601 video::ITexture *rtt = generateTextureFromMesh(
1602 cube, device, dim, rtt_texture_name,
1605 camera_projection_matrix,
1614 // Free textures of images
1615 driver->removeTexture(texture_top);
1616 driver->removeTexture(texture_left);
1617 driver->removeTexture(texture_right);
1621 baseimg = generate_image_from_scratch(
1622 imagename_top, device, sourcecache);
1626 // Create image of render target
1627 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1630 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1634 image->copyTo(baseimg);
1639 [lowpart:percent:filename
1640 Adds the lower part of a texture
1642 else if(part_of_name.substr(0,9) == "[lowpart:")
1644 Strfnd sf(part_of_name);
1646 u32 percent = stoi(sf.next(":"));
1647 std::string filename = sf.next(":");
1648 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1651 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1652 video::IImage *img = sourcecache->getOrLoad(filename, device);
1655 core::dimension2d<u32> dim = img->getDimension();
1656 core::position2d<s32> pos_base(0, 0);
1657 video::IImage *img2 =
1658 driver->createImage(video::ECF_A8R8G8B8, dim);
1661 core::position2d<s32> clippos(0, 0);
1662 clippos.Y = dim.Height * (100-percent) / 100;
1663 core::dimension2d<u32> clipdim = dim;
1664 clipdim.Height = clipdim.Height * percent / 100 + 1;
1665 core::rect<s32> cliprect(clippos, clipdim);
1666 img2->copyToWithAlpha(baseimg, pos_base,
1667 core::rect<s32>(v2s32(0,0), dim),
1668 video::SColor(255,255,255,255),
1675 Crops a frame of a vertical animation.
1676 N = frame count, I = frame index
1678 else if(part_of_name.substr(0,15) == "[verticalframe:")
1680 Strfnd sf(part_of_name);
1682 u32 frame_count = stoi(sf.next(":"));
1683 u32 frame_index = stoi(sf.next(":"));
1685 if(baseimg == NULL){
1686 errorstream<<"generate_image(): baseimg!=NULL "
1687 <<"for part_of_name=\""<<part_of_name
1688 <<"\", cancelling."<<std::endl;
1692 v2u32 frame_size = baseimg->getDimension();
1693 frame_size.Y /= frame_count;
1695 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1698 errorstream<<"generate_image(): Could not create image "
1699 <<"for part_of_name=\""<<part_of_name
1700 <<"\", cancelling."<<std::endl;
1704 // Fill target image with transparency
1705 img->fill(video::SColor(0,0,0,0));
1707 core::dimension2d<u32> dim = frame_size;
1708 core::position2d<s32> pos_dst(0, 0);
1709 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1710 baseimg->copyToWithAlpha(img, pos_dst,
1711 core::rect<s32>(pos_src, dim),
1712 video::SColor(255,255,255,255),
1720 errorstream<<"generate_image(): Invalid "
1721 " modification: \""<<part_of_name<<"\""<<std::endl;
1728 void overlay(video::IImage *image, video::IImage *overlay)
1731 Copy overlay to image, taking alpha into account.
1732 Where image is transparent, don't copy from overlay.
1733 Images sizes must be identical.
1735 if(image == NULL || overlay == NULL)
1738 core::dimension2d<u32> dim = image->getDimension();
1739 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1740 assert(dim == dim_overlay);
1742 for(u32 y=0; y<dim.Height; y++)
1743 for(u32 x=0; x<dim.Width; x++)
1745 video::SColor c1 = image->getPixel(x,y);
1746 video::SColor c2 = overlay->getPixel(x,y);
1747 u32 a1 = c1.getAlpha();
1748 u32 a2 = c2.getAlpha();
1749 if(a1 == 255 && a2 != 0)
1751 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1752 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1753 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1755 image->setPixel(x,y,c1);
1760 Draw an image on top of an another one, using the alpha channel of the
1763 This exists because IImage::copyToWithAlpha() doesn't seem to always
1766 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1767 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1769 for(u32 y0=0; y0<size.Y; y0++)
1770 for(u32 x0=0; x0<size.X; x0++)
1772 s32 src_x = src_pos.X + x0;
1773 s32 src_y = src_pos.Y + y0;
1774 s32 dst_x = dst_pos.X + x0;
1775 s32 dst_y = dst_pos.Y + y0;
1776 video::SColor src_c = src->getPixel(src_x, src_y);
1777 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1778 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1779 dst->setPixel(dst_x, dst_y, dst_c);
1783 void brighten(video::IImage *image)
1788 core::dimension2d<u32> dim = image->getDimension();
1790 for(u32 y=0; y<dim.Height; y++)
1791 for(u32 x=0; x<dim.Width; x++)
1793 video::SColor c = image->getPixel(x,y);
1794 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1795 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1796 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1797 image->setPixel(x,y,c);
1801 u32 parseImageTransform(const std::string& s)
1803 int total_transform = 0;
1805 std::string transform_names[8];
1806 transform_names[0] = "i";
1807 transform_names[1] = "r90";
1808 transform_names[2] = "r180";
1809 transform_names[3] = "r270";
1810 transform_names[4] = "fx";
1811 transform_names[6] = "fy";
1813 std::size_t pos = 0;
1814 while(pos < s.size())
1817 for(int i = 0; i <= 7; ++i)
1819 const std::string &name_i = transform_names[i];
1821 if(s[pos] == ('0' + i))
1827 else if(!(name_i.empty()) &&
1828 lowercase(s.substr(pos, name_i.size())) == name_i)
1831 pos += name_i.size();
1838 // Multiply total_transform and transform in the group D4
1841 new_total = (transform + total_transform) % 4;
1843 new_total = (transform - total_transform + 8) % 4;
1844 if((transform >= 4) ^ (total_transform >= 4))
1847 total_transform = new_total;
1849 return total_transform;
1852 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1854 if(transform % 2 == 0)
1857 return core::dimension2d<u32>(dim.Height, dim.Width);
1860 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1862 if(src == NULL || dst == NULL)
1865 core::dimension2d<u32> srcdim = src->getDimension();
1866 core::dimension2d<u32> dstdim = dst->getDimension();
1868 assert(dstdim == imageTransformDimension(transform, srcdim));
1869 assert(transform >= 0 && transform <= 7);
1872 Compute the transformation from source coordinates (sx,sy)
1873 to destination coordinates (dx,dy).
1877 if(transform == 0) // identity
1878 sxn = 0, syn = 2; // sx = dx, sy = dy
1879 else if(transform == 1) // rotate by 90 degrees ccw
1880 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1881 else if(transform == 2) // rotate by 180 degrees
1882 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1883 else if(transform == 3) // rotate by 270 degrees ccw
1884 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1885 else if(transform == 4) // flip x
1886 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1887 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1888 sxn = 2, syn = 0; // sx = dy, sy = dx
1889 else if(transform == 6) // flip y
1890 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1891 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1892 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1894 for(u32 dy=0; dy<dstdim.Height; dy++)
1895 for(u32 dx=0; dx<dstdim.Width; dx++)
1897 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1898 u32 sx = entries[sxn];
1899 u32 sy = entries[syn];
1900 video::SColor c = src->getPixel(sx,sy);
1901 dst->setPixel(dx,dy,c);