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 Adds a new texture to the video driver and returns a pointer to it.
523 This pointer should not be dropped. Any texture that was registered
524 with that name before is removed (this may invalidate some ITexture
527 video::ITexture* register_texture(video::IVideoDriver *driver,
528 std::string name, video::IImage *img);
531 Generate image based on a string like "stone.png" or "[crack0".
532 if baseimg is NULL, it is created. Otherwise stuff is made on it.
534 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
535 IrrlichtDevice *device, SourceImageCache *sourcecache);
538 Generates an image from a full string like
539 "stone.png^mineral_coal.png^[crack0".
541 This is used by buildMainAtlas().
543 video::IImage* generate_image_from_scratch(std::string name,
544 IrrlichtDevice *device, SourceImageCache *sourcecache);
547 This method generates all the textures
549 u32 TextureSource::getTextureIdDirect(const std::string &name)
551 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
553 // Empty name means texture 0
556 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
561 Calling only allowed from main thread
563 if(get_current_thread_id() != m_main_thread)
565 errorstream<<"TextureSource::getTextureIdDirect() "
566 "called not from main thread"<<std::endl;
571 See if texture already exists
574 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
576 core::map<std::string, u32>::Node *n;
577 n = m_name_to_id.find(name);
580 /*infostream<<"getTextureIdDirect(): \""<<name
581 <<"\" found in cache"<<std::endl;*/
582 return n->getValue();
586 /*infostream<<"getTextureIdDirect(): \""<<name
587 <<"\" NOT found in cache. Creating it."<<std::endl;*/
593 char separator = '^';
596 This is set to the id of the base image.
597 If left 0, there is no base image and a completely new image
600 u32 base_image_id = 0;
602 // Find last meta separator in name
603 s32 last_separator_position = -1;
604 for(s32 i=name.size()-1; i>=0; i--)
606 if(name[i] == separator)
608 last_separator_position = i;
613 If separator was found, construct the base name and make the
614 base image using a recursive call
616 std::string base_image_name;
617 if(last_separator_position != -1)
619 // Construct base name
620 base_image_name = name.substr(0, last_separator_position);
621 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
622 " to get base image of \""<<name<<"\" = \""
623 <<base_image_name<<"\""<<std::endl;*/
624 base_image_id = getTextureIdDirect(base_image_name);
627 //infostream<<"base_image_id="<<base_image_id<<std::endl;
629 video::IVideoDriver* driver = m_device->getVideoDriver();
632 video::ITexture *t = NULL;
635 An image will be built from files and then converted into a texture.
637 video::IImage *baseimg = NULL;
639 // If a base image was found, copy it to baseimg
640 if(base_image_id != 0)
642 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
644 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
646 video::IImage *image = ap.atlas_img;
650 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
651 <<"cache: \""<<base_image_name<<"\""
656 core::dimension2d<u32> dim = ap.intsize;
658 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
660 core::position2d<s32> pos_to(0,0);
661 core::position2d<s32> pos_from = ap.intpos;
665 v2s32(0,0), // position in target
666 core::rect<s32>(pos_from, dim) // from
669 /*infostream<<"getTextureIdDirect(): Loaded \""
670 <<base_image_name<<"\" from image cache"
676 Parse out the last part of the name of the image and act
680 std::string last_part_of_name = name.substr(last_separator_position+1);
681 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
683 // Generate image according to part of name
684 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
686 errorstream<<"getTextureIdDirect(): "
687 "failed to generate \""<<last_part_of_name<<"\""
691 // If no resulting image, print a warning
694 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
695 " create texture \""<<name<<"\""<<std::endl;
698 // Create texture from resulting image
700 t = register_texture(driver, name, baseimg);
703 Add texture to caches (add NULL textures too)
706 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
708 u32 id = m_atlaspointer_cache.size();
714 core::dimension2d<u32> baseimg_dim(0,0);
716 baseimg_dim = baseimg->getDimension();
717 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
718 m_atlaspointer_cache.push_back(nap);
719 m_name_to_id.insert(name, id);
721 /*infostream<<"getTextureIdDirect(): "
722 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
727 std::string TextureSource::getTextureName(u32 id)
729 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
731 if(id >= m_atlaspointer_cache.size())
733 errorstream<<"TextureSource::getTextureName(): id="<<id
734 <<" >= m_atlaspointer_cache.size()="
735 <<m_atlaspointer_cache.size()<<std::endl;
739 return m_atlaspointer_cache[id].name;
743 AtlasPointer TextureSource::getTexture(u32 id)
745 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
747 if(id >= m_atlaspointer_cache.size())
748 return AtlasPointer(0, NULL);
750 return m_atlaspointer_cache[id].a;
753 void TextureSource::updateAP(AtlasPointer &ap)
755 AtlasPointer ap2 = getTexture(ap.id);
759 void TextureSource::processQueue()
764 if(m_get_texture_queue.size() > 0)
766 GetRequest<std::string, u32, u8, u8>
767 request = m_get_texture_queue.pop();
769 /*infostream<<"TextureSource::processQueue(): "
770 <<"got texture request with "
771 <<"name=\""<<request.key<<"\""
774 GetResult<std::string, u32, u8, u8>
776 result.key = request.key;
777 result.callers = request.callers;
778 result.item = getTextureIdDirect(request.key);
780 request.dest->push_back(result);
784 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
786 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
788 assert(get_current_thread_id() == m_main_thread);
790 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
793 void TextureSource::rebuildImagesAndTextures()
795 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
797 /*// Oh well... just clear everything, they'll load sometime.
798 m_atlaspointer_cache.clear();
799 m_name_to_id.clear();*/
801 video::IVideoDriver* driver = m_device->getVideoDriver();
803 // Remove source images from textures to disable inheriting textures
804 // from existing textures
805 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
806 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
807 sap->atlas_img->drop();
808 sap->atlas_img = NULL;
812 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
813 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
815 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
816 // Create texture from resulting image
817 video::ITexture *t = NULL;
819 t = register_texture(driver, sap->name, img);
823 sap->a.pos = v2f(0,0);
824 sap->a.size = v2f(1,1);
826 sap->atlas_img = img;
827 sap->intpos = v2s32(0,0);
828 sap->intsize = img->getDimension();
832 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
834 assert(gamedef->tsrc() == this);
835 INodeDefManager *ndef = gamedef->ndef();
837 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
839 //return; // Disable (for testing)
841 video::IVideoDriver* driver = m_device->getVideoDriver();
844 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
846 // Create an image of the right size
847 core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
848 core::dimension2d<u32> atlas_dim(2048,2048);
849 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
850 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
851 video::IImage *atlas_img =
852 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
854 if(atlas_img == NULL)
856 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
857 "image; not building texture atlas."<<std::endl;
862 Grab list of stuff to include in the texture atlas from the
863 main content features
866 core::map<std::string, bool> sourcelist;
868 for(u16 j=0; j<MAX_CONTENT+1; j++)
870 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
872 const ContentFeatures &f = ndef->get(j);
873 for(u32 i=0; i<6; i++)
875 std::string name = f.tiledef[i].name;
876 sourcelist[name] = true;
880 infostream<<"Creating texture atlas out of textures: ";
881 for(core::map<std::string, bool>::Iterator
882 i = sourcelist.getIterator();
883 i.atEnd() == false; i++)
885 std::string name = i.getNode()->getKey();
886 infostream<<"\""<<name<<"\" ";
888 infostream<<std::endl;
890 // Padding to disallow texture bleeding
891 // (16 needed if mipmapping is used; otherwise less will work too)
893 s32 column_padding = 16;
894 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
897 First pass: generate almost everything
899 core::position2d<s32> pos_in_atlas(0,0);
901 pos_in_atlas.X = column_padding;
902 pos_in_atlas.Y = padding;
904 for(core::map<std::string, bool>::Iterator
905 i = sourcelist.getIterator();
906 i.atEnd() == false; i++)
908 std::string name = i.getNode()->getKey();
910 // Generate image by name
911 video::IImage *img2 = generate_image_from_scratch(name, m_device,
915 errorstream<<"TextureSource::buildMainAtlas(): "
916 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
920 core::dimension2d<u32> dim = img2->getDimension();
922 // Don't add to atlas if image is too large
923 core::dimension2d<u32> max_size_in_atlas(64,64);
924 if(dim.Width > max_size_in_atlas.Width
925 || dim.Height > max_size_in_atlas.Height)
927 infostream<<"TextureSource::buildMainAtlas(): Not adding "
928 <<"\""<<name<<"\" because image is large"<<std::endl;
932 // Wrap columns and stop making atlas if atlas is full
933 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
935 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
936 errorstream<<"TextureSource::buildMainAtlas(): "
937 <<"Atlas is full, not adding more textures."
941 pos_in_atlas.Y = padding;
942 pos_in_atlas.X += column_width + column_padding*2;
945 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
946 <<"\" to texture atlas"<<std::endl;*/
948 // Tile it a few times in the X direction
949 u16 xwise_tiling = column_width / dim.Width;
950 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
952 for(u32 j=0; j<xwise_tiling; j++)
954 // Copy the copy to the atlas
955 /*img2->copyToWithAlpha(atlas_img,
956 pos_in_atlas + v2s32(j*dim.Width,0),
957 core::rect<s32>(v2s32(0,0), dim),
958 video::SColor(255,255,255,255),
960 img2->copyTo(atlas_img,
961 pos_in_atlas + v2s32(j*dim.Width,0),
962 core::rect<s32>(v2s32(0,0), dim),
966 // Copy the borders a few times to disallow texture bleeding
967 for(u32 side=0; side<2; side++) // top and bottom
968 for(s32 y0=0; y0<padding; y0++)
969 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
975 dst_y = y0 + pos_in_atlas.Y + dim.Height;
976 src_y = pos_in_atlas.Y + dim.Height - 1;
980 dst_y = -y0 + pos_in_atlas.Y-1;
981 src_y = pos_in_atlas.Y;
983 s32 x = x0 + pos_in_atlas.X;
984 video::SColor c = atlas_img->getPixel(x, src_y);
985 atlas_img->setPixel(x,dst_y,c);
988 for(u32 side=0; side<2; side++) // left and right
989 for(s32 x0=0; x0<column_padding; x0++)
990 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
996 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
997 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
1001 dst_x = -x0 + pos_in_atlas.X-1;
1002 src_x = pos_in_atlas.X;
1004 s32 y = y0 + pos_in_atlas.Y;
1005 s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
1007 video::SColor c = atlas_img->getPixel(src_x, src_y);
1008 atlas_img->setPixel(dst_x,dst_y,c);
1014 Add texture to caches
1017 bool reuse_old_id = false;
1018 u32 id = m_atlaspointer_cache.size();
1019 // Check old id without fetching a texture
1020 core::map<std::string, u32>::Node *n;
1021 n = m_name_to_id.find(name);
1022 // If it exists, we will replace the old definition
1025 reuse_old_id = true;
1026 /*infostream<<"TextureSource::buildMainAtlas(): "
1027 <<"Replacing old AtlasPointer"<<std::endl;*/
1030 // Create AtlasPointer
1031 AtlasPointer ap(id);
1032 ap.atlas = NULL; // Set on the second pass
1033 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1034 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1035 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1036 (float)dim.Width/(float)atlas_dim.Height);
1037 ap.tiled = xwise_tiling;
1039 // Create SourceAtlasPointer and add to containers
1040 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1042 m_atlaspointer_cache[id] = nap;
1044 m_atlaspointer_cache.push_back(nap);
1045 m_name_to_id[name] = id;
1047 // Increment position
1048 pos_in_atlas.Y += dim.Height + padding * 2;
1054 video::ITexture *t = register_texture(driver, "__main_atlas__", atlas_img);
1058 Second pass: set texture pointer in generated AtlasPointers
1060 for(core::map<std::string, bool>::Iterator
1061 i = sourcelist.getIterator();
1062 i.atEnd() == false; i++)
1064 std::string name = i.getNode()->getKey();
1065 if(m_name_to_id.find(name) == NULL)
1067 u32 id = m_name_to_id[name];
1068 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1069 m_atlaspointer_cache[id].a.atlas = t;
1073 Write image to file so that it can be inspected
1075 /*std::string atlaspath = porting::path_user
1076 + DIR_DELIM + "generated_texture_atlas.png";
1077 infostream<<"Removing and writing texture atlas for inspection to "
1078 <<atlaspath<<std::endl;
1079 fs::RecursiveDelete(atlaspath);
1080 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1083 video::IImage* generate_image_from_scratch(std::string name,
1084 IrrlichtDevice *device, SourceImageCache *sourcecache)
1086 /*infostream<<"generate_image_from_scratch(): "
1087 "\""<<name<<"\""<<std::endl;*/
1089 video::IVideoDriver* driver = device->getVideoDriver();
1096 video::IImage *baseimg = NULL;
1098 char separator = '^';
1100 // Find last meta separator in name
1101 s32 last_separator_position = name.find_last_of(separator);
1102 //if(last_separator_position == std::npos)
1103 // last_separator_position = -1;
1105 /*infostream<<"generate_image_from_scratch(): "
1106 <<"last_separator_position="<<last_separator_position
1110 If separator was found, construct the base name and make the
1111 base image using a recursive call
1113 std::string base_image_name;
1114 if(last_separator_position != -1)
1116 // Construct base name
1117 base_image_name = name.substr(0, last_separator_position);
1118 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1119 " to get base image of \""<<name<<"\" = \""
1120 <<base_image_name<<"\""<<std::endl;*/
1121 baseimg = generate_image_from_scratch(base_image_name, device,
1126 Parse out the last part of the name of the image and act
1130 std::string last_part_of_name = name.substr(last_separator_position+1);
1131 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1133 // Generate image according to part of name
1134 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1136 errorstream<<"generate_image_from_scratch(): "
1137 "failed to generate \""<<last_part_of_name<<"\""
1145 video::ITexture* register_texture(video::IVideoDriver *driver,
1146 std::string name, video::IImage *img)
1148 video::ITexture *old_texture = driver->findTexture(name.c_str());
1150 driver->removeTexture(old_texture);
1151 return driver->addTexture(name.c_str(), img);
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 = register_texture(driver,
1561 imagename_top + "__temp1__", img_top);
1562 video::ITexture *texture_left = register_texture(driver,
1563 imagename_left + "__temp2__", img_left);
1564 video::ITexture *texture_right = register_texture(driver,
1565 imagename_right + "__temp3__", 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);