X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Ftile.cpp;h=aea9665f5eb69ca3568c12447538a81e7a1b8771;hb=2bb559be826f4f82b56fb7916e206df55337c258;hp=1d5f4d8338c97ec1d4237c5a3c5e09de7dfbddb7;hpb=6a76c226e10e92c3e3339096f07f8ab065e2098b;p=minetest.git diff --git a/src/tile.cpp b/src/tile.cpp index 1d5f4d833..aea9665f5 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,18 +1,18 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License along +You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ @@ -21,15 +21,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "debug.h" #include "main.h" // for g_settings #include "filesys.h" -#include "utility.h" #include "settings.h" #include "mesh.h" #include #include "log.h" #include "mapnode.h" // For texture atlas making -#include "mineral.h" // For texture atlas making #include "nodedef.h" // For texture atlas making #include "gamedef.h" +#include "util/string.h" +#include "util/container.h" +#include "util/thread.h" +#include "util/numeric.h" /* A cache from texture name to texture path @@ -83,7 +85,10 @@ static std::string getImagePath(std::string path) "pcx", "ppm", "psd", "wal", "rgb", NULL }; - + // If there is no extension, add one + if(removeStringEnd(path, extensions) == "") + path = path + ".png"; + // Check paths until something is found to exist const char **ext = extensions; do{ bool r = replace_ext(path, *ext); @@ -128,13 +133,26 @@ std::string getTexturePath(const std::string &filename) fullpath = getImagePath(testpath); } + /* + Check from $user/textures/all + */ + if(fullpath == "") + { + std::string texture_path = porting::path_user + DIR_DELIM + + "textures" + DIR_DELIM + "all"; + std::string testpath = texture_path + DIR_DELIM + filename; + // Check all filename extensions. Returns "" if not found. + fullpath = getImagePath(testpath); + } + /* Check from default data directory */ if(fullpath == "") { - std::string rel_path = std::string("clienttextures")+DIR_DELIM+filename; - std::string testpath = porting::path_data + DIR_DELIM + rel_path; + std::string base_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack"; + std::string testpath = base_path + DIR_DELIM + filename; // Check all filename extensions. Returns "" if not found. fullpath = getImagePath(testpath); } @@ -188,10 +206,10 @@ class SourceImageCache { assert(img); // Remove old image - core::map::Node *n; + std::map::iterator n; n = m_images.find(name); - if(n){ - video::IImage *oldimg = n->getValue(); + if(n != m_images.end()){ + video::IImage *oldimg = n->second; if(oldimg) oldimg->drop(); } @@ -211,20 +229,20 @@ class SourceImageCache } video::IImage* get(const std::string &name) { - core::map::Node *n; + std::map::iterator n; n = m_images.find(name); - if(n) - return n->getValue(); + if(n != m_images.end()) + return n->second; return NULL; } // Primarily fetches from cache, secondarily tries to read from filesystem video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device) { - core::map::Node *n; + std::map::iterator n; n = m_images.find(name); - if(n){ - n->getValue()->grab(); // Grab for caller - return n->getValue(); + if(n != m_images.end()){ + n->second->grab(); // Grab for caller + return n->second; } video::IVideoDriver* driver = device->getVideoDriver(); std::string path = getTexturePath(name.c_str()); @@ -245,7 +263,7 @@ class SourceImageCache return img; } private: - core::map m_images; + std::map m_images; }; /* @@ -279,12 +297,14 @@ class TextureSource : public IWritableTextureSource Example case #2: - Assume a texture with the id 1 exists, and has the name "stone.png^mineral1" and is specified as a part of some atlas. - - Now MapBlock::getNodeTile() stumbles upon a node which uses - texture id 1, and finds out that NODEMOD_CRACK must be applied - with progression=0 - - It finds out the name of the texture with getTextureName(1), + - Now getNodeTile() stumbles upon a node which uses + texture id 1, and determines that MATERIAL_FLAG_CRACK + must be applied to the tile + - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and + has received the current crack level 0 from the client. It + finds out the name of the texture with getTextureName(1), appends "^crack0" to it and gets a new texture id with - getTextureId("stone.png^mineral1^crack0") + getTextureId("stone.png^mineral1^crack0"). */ @@ -299,8 +319,8 @@ class TextureSource : public IWritableTextureSource Example names: "stone.png" "stone.png^crack2" - "stone.png^blit:mineral_coal.png" - "stone.png^blit:mineral_coal.png^crack1" + "stone.png^mineral_coal.png" + "stone.png^mineral_coal.png^crack1" - If texture specified by name is found from cache, return the cached id. @@ -337,6 +357,12 @@ class TextureSource : public IWritableTextureSource return ap.atlas; } + // Gets a separate texture atlas pointer + AtlasPointer getTextureRawAP(const AtlasPointer &ap) + { + return getTexture(getTextureName(ap.id) + "^[forcesingle"); + } + // Returns a pointer to the irrlicht device virtual IrrlichtDevice* getDevice() { @@ -346,6 +372,18 @@ class TextureSource : public IWritableTextureSource // Update new texture pointer and texture coordinates to an // AtlasPointer based on it's texture id void updateAP(AtlasPointer &ap); + + bool isKnownSourceImage(const std::string &name) + { + bool is_known = false; + bool cache_found = m_source_image_existence.get(name, &is_known); + if(cache_found) + return is_known; + // Not found in cache; find out if a local file exists + is_known = (getTexturePath(name) != ""); + m_source_image_existence.set(name, is_known); + return is_known; + } // Processes queued texture requests from other threads. // Shall be called from the main thread. @@ -374,11 +412,14 @@ class TextureSource : public IWritableTextureSource // This should be only accessed from the main thread SourceImageCache m_sourcecache; + // Thread-safe cache of what source images are known (true = known) + MutexedMap m_source_image_existence; + // A texture id is index in this array. // The first position contains a NULL texture. - core::array m_atlaspointer_cache; + std::vector m_atlaspointer_cache; // Maps a texture name to an index in the former. - core::map m_name_to_id; + std::map m_name_to_id; // The two former containers are behind this mutex JMutex m_atlaspointer_cache_mutex; @@ -424,11 +465,11 @@ u32 TextureSource::getTextureId(const std::string &name) See if texture already exists */ JMutexAutoLock lock(m_atlaspointer_cache_mutex); - core::map::Node *n; + std::map::iterator n; n = m_name_to_id.find(name); - if(n != NULL) + if(n != m_name_to_id.end()) { - return n->getValue(); + return n->second; } } @@ -475,8 +516,22 @@ u32 TextureSource::getTextureId(const std::string &name) return 0; } +// Overlay image on top of another image (used for cracks) +void overlay(video::IImage *image, video::IImage *overlay); + +// Draw an image on top of an another one, using the alpha channel of the +// source image +static void blit_with_alpha(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size); + // Brighten image void brighten(video::IImage *image); +// Parse a transform name +u32 parseImageTransform(const std::string& s); +// Apply transform to image dimension +core::dimension2d imageTransformDimension(u32 transform, core::dimension2d dim); +// Apply transform to image data +void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); /* Generate image based on a string like "stone.png" or "[crack0". @@ -524,13 +579,13 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) { JMutexAutoLock lock(m_atlaspointer_cache_mutex); - core::map::Node *n; + std::map::iterator n; n = m_name_to_id.find(name); - if(n != NULL) + if(n != m_name_to_id.end()) { /*infostream<<"getTextureIdDirect(): \""<getValue(); + return n->second; } } @@ -669,7 +724,7 @@ u32 TextureSource::getTextureIdDirect(const std::string &name) baseimg_dim = baseimg->getDimension(); SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim); m_atlaspointer_cache.push_back(nap); - m_name_to_id.insert(name, id); + m_name_to_id[name] = id; /*infostream<<"getTextureIdDirect(): " <<"Returning id="< 0) + if(!m_get_texture_queue.empty()) { GetRequest request = m_get_texture_queue.pop(); @@ -741,6 +796,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im assert(get_current_thread_id() == m_main_thread); m_sourcecache.insert(name, img, true, m_device->getVideoDriver()); + m_source_image_existence.set(name, true); } void TextureSource::rebuildImagesAndTextures() @@ -797,7 +853,10 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) JMutexAutoLock lock(m_atlaspointer_cache_mutex); // Create an image of the right size - core::dimension2d atlas_dim(1024,1024); + core::dimension2d max_dim = driver->getMaxTextureSize(); + core::dimension2d atlas_dim(2048,2048); + atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width); + atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height); video::IImage *atlas_img = driver->createImage(video::ECF_A8R8G8B8, atlas_dim); //assert(atlas_img); @@ -813,7 +872,7 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) main content features */ - core::map sourcelist; + std::set sourcelist; for(u16 j=0; jget(j); for(u32 i=0; i<6; i++) { - std::string name = f.tname_tiles[i]; - sourcelist[name] = true; - - if(f.param_type == CPT_MINERAL){ - for(int k=1; k::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) + for(std::set::iterator + i = sourcelist.begin(); + i != sourcelist.end(); ++i) { - std::string name = i.getNode()->getKey(); + std::string name = *i; infostream<<"\""< pos_in_atlas(0,0); + pos_in_atlas.X = column_padding; pos_in_atlas.Y = padding; - for(core::map::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) + for(std::set::iterator + i = sourcelist.begin(); + i != sourcelist.end(); ++i) { - std::string name = i.getNode()->getKey(); + std::string name = *i; // Generate image by name video::IImage *img2 = generate_image_from_scratch(name, m_device, @@ -876,8 +928,8 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) core::dimension2d dim = img2->getDimension(); - // Don't add to atlas if image is large - core::dimension2d max_size_in_atlas(32,32); + // Don't add to atlas if image is too large + core::dimension2d max_size_in_atlas(64,64); if(dim.Width > max_size_in_atlas.Width || dim.Height > max_size_in_atlas.Height) { @@ -889,14 +941,14 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) // Wrap columns and stop making atlas if atlas is full if(pos_in_atlas.Y + dim.Height > atlas_dim.Height) { - if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){ + if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){ errorstream<<"TextureSource::buildMainAtlas(): " <<"Atlas is full, not adding more textures." <setPixel(x,dst_y,c); } + for(u32 side=0; side<2; side++) // left and right + for(s32 x0=0; x0getPixel(src_x, src_y); + atlas_img->setPixel(dst_x,dst_y,c); + } + img2->drop(); /* @@ -951,11 +1026,11 @@ void TextureSource::buildMainAtlas(class IGameDef *gamedef) bool reuse_old_id = false; u32 id = m_atlaspointer_cache.size(); // Check old id without fetching a texture - core::map::Node *n; + std::map::iterator n; n = m_name_to_id.find(name); // If it exists, we will replace the old definition - if(n){ - id = n->getValue(); + if(n != m_name_to_id.end()){ + id = n->second; reuse_old_id = true; /*infostream<<"TextureSource::buildMainAtlas(): " <<"Replacing old AtlasPointer"<::Iterator - i = sourcelist.getIterator(); - i.atEnd() == false; i++) + for(std::set::iterator + i = sourcelist.begin(); + i != sourcelist.end(); ++i) { - std::string name = i.getNode()->getKey(); - if(m_name_to_id.find(name) == NULL) + std::string name = *i; + if(m_name_to_id.find(name) == m_name_to_id.end()) continue; u32 id = m_name_to_id[name]; //infostream<<"id of name "<=0; i--) - { - if(name[i] == separator) - { - last_separator_position = i; - break; - } - } + s32 last_separator_position = name.find_last_of(separator); + //if(last_separator_position == std::npos) + // last_separator_position = -1; /*infostream<<"generate_image_from_scratch(): " <<"last_separator_position="< pos_from(0,0); // Blit - image->copyToWithAlpha(baseimg, pos_to, + /*image->copyToWithAlpha(baseimg, pos_to, core::rect(pos_from, dim), video::SColor(255,255,255,255), - NULL); + NULL);*/ + blit_with_alpha(image, baseimg, pos_from, pos_to, dim); // Drop image image->drop(); } @@ -1197,8 +1267,19 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, return false; } - // Crack image number - u16 progression = stoi(part_of_name.substr(6)); + // Crack image number and overlay option + s32 progression = 0; + bool use_overlay = false; + if(part_of_name.substr(6,1) == "o") + { + progression = stoi(part_of_name.substr(7)); + use_overlay = true; + } + else + { + progression = stoi(part_of_name.substr(6)); + use_overlay = false; + } // Size of the base image core::dimension2d dim_base = baseimg->getDimension(); @@ -1209,67 +1290,61 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, It is an image with a number of cracking stages horizontally tiled. */ - video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device); + video::IImage *img_crack = sourcecache->getOrLoad( + "crack_anylength.png", device); - if(img_crack) + if(img_crack && progression >= 0) { // Dimension of original image core::dimension2d dim_crack = img_crack->getDimension(); // Count of crack stages - u32 crack_count = dim_crack.Height / dim_crack.Width; + s32 crack_count = dim_crack.Height / dim_crack.Width; // Limit progression if(progression > crack_count-1) progression = crack_count-1; - // Dimension of a single scaled crack stage - core::dimension2d dim_crack_scaled_single( - dim_base.Width, - dim_base.Height - ); - // Dimension of scaled size - core::dimension2d dim_crack_scaled( - dim_crack_scaled_single.Width, - dim_crack_scaled_single.Height * crack_count + // Dimension of a single crack stage + core::dimension2d dim_crack_cropped( + dim_crack.Width, + dim_crack.Width ); - // Create scaled crack image + // Create cropped and scaled crack images + video::IImage *img_crack_cropped = driver->createImage( + video::ECF_A8R8G8B8, dim_crack_cropped); video::IImage *img_crack_scaled = driver->createImage( - video::ECF_A8R8G8B8, dim_crack_scaled); - if(img_crack_scaled) + video::ECF_A8R8G8B8, dim_base); + + if(img_crack_cropped && img_crack_scaled) { + // Crop crack image + v2s32 pos_crack(0, progression*dim_crack.Width); + img_crack->copyTo(img_crack_cropped, + v2s32(0,0), + core::rect(pos_crack, dim_crack_cropped)); // Scale crack image by copying - img_crack->copyToScaling(img_crack_scaled); - - // Position to copy the crack from - core::position2d pos_crack_scaled( - 0, - dim_crack_scaled_single.Height * progression - ); - - // This tiling does nothing currently but is useful - for(u32 y0=0; y0copyToScaling(img_crack_scaled); + // Copy or overlay crack image + if(use_overlay) + { + overlay(baseimg, img_crack_scaled); + } + else { - // Position to copy the crack to in the base image - core::position2d pos_base( - x0*dim_crack_scaled_single.Width, - y0*dim_crack_scaled_single.Height - ); - // Rectangle to copy the crack from on the scaled image - core::rect rect_crack_scaled( - pos_crack_scaled, - dim_crack_scaled_single - ); - // Copy it - img_crack_scaled->copyToWithAlpha(baseimg, pos_base, - rect_crack_scaled, - video::SColor(255,255,255,255), - NULL); + /*img_crack_scaled->copyToWithAlpha( + baseimg, + v2s32(0,0), + core::rect(v2s32(0,0), dim_base), + video::SColor(255,255,255,255));*/ + blit_with_alpha(img_crack_scaled, baseimg, + v2s32(0,0), v2s32(0,0), dim_base); } + } + if(img_crack_scaled) img_crack_scaled->drop(); - } + + if(img_crack_cropped) + img_crack_cropped->drop(); img_crack->drop(); } @@ -1286,7 +1361,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, u32 h0 = stoi(sf.next(":")); infostream<<"combined w="<createImage(video::ECF_A8R8G8B8, dim); img->copyTo(img2); img->drop(); - img2->copyToWithAlpha(baseimg, pos_base, + /*img2->copyToWithAlpha(baseimg, pos_base, core::rect(v2s32(0,0), dim), video::SColor(255,255,255,255), - NULL); + NULL);*/ + blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); img2->drop(); } else @@ -1402,6 +1482,46 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, baseimg->setPixel(x,y,c); } } + /* + "[transformN" + Rotates and/or flips the image. + + N can be a number (between 0 and 7) or a transform name. + Rotations are counter-clockwise. + 0 I identity + 1 R90 rotate by 90 degrees + 2 R180 rotate by 180 degrees + 3 R270 rotate by 270 degrees + 4 FX flip X + 5 FXR90 flip X then rotate by 90 degrees + 6 FY flip Y + 7 FYR90 flip Y then rotate by 90 degrees + + Note: Transform names can be concatenated to produce + their product (applies the first then the second). + The resulting transform will be equivalent to one of the + eight existing ones, though (see: dihedral group). + */ + else if(part_of_name.substr(0,10) == "[transform") + { + if(baseimg == NULL) + { + errorstream<<"generate_image(): baseimg==NULL " + <<"for part_of_name=\""< dim = imageTransformDimension( + transform, baseimg->getDimension()); + video::IImage *image = driver->createImage( + baseimg->getColorFormat(), dim); + assert(image); + imageTransform(transform, baseimg, image); + baseimg->drop(); + baseimg = image; + } /* [inventorycube{topimage{leftimage{rightimage In every subimage, replace ^ with &. @@ -1498,8 +1618,6 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, if(rtt == NULL) { - errorstream<<"generate_image(): render to texture failed." - " Creating fallback image"<drop(); } } + /* + [lowpart:percent:filename + Adds the lower part of a texture + */ + else if(part_of_name.substr(0,9) == "[lowpart:") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 percent = stoi(sf.next(":")); + std::string filename = sf.next(":"); + //infostream<<"power part "<createImage(video::ECF_A8R8G8B8, v2u32(16,16)); + video::IImage *img = sourcecache->getOrLoad(filename, device); + if(img) + { + core::dimension2d dim = img->getDimension(); + core::position2d pos_base(0, 0); + video::IImage *img2 = + driver->createImage(video::ECF_A8R8G8B8, dim); + img->copyTo(img2); + img->drop(); + core::position2d clippos(0, 0); + clippos.Y = dim.Height * (100-percent) / 100; + core::dimension2d clipdim = dim; + clipdim.Height = clipdim.Height * percent / 100 + 1; + core::rect cliprect(clippos, clipdim); + img2->copyToWithAlpha(baseimg, pos_base, + core::rect(v2s32(0,0), dim), + video::SColor(255,255,255,255), + &cliprect); + img2->drop(); + } + } + /* + [verticalframe:N:I + Crops a frame of a vertical animation. + N = frame count, I = frame index + */ + else if(part_of_name.substr(0,15) == "[verticalframe:") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 frame_count = stoi(sf.next(":")); + u32 frame_index = stoi(sf.next(":")); + + if(baseimg == NULL){ + errorstream<<"generate_image(): baseimg!=NULL " + <<"for part_of_name=\""<getDimension(); + frame_size.Y /= frame_count; + + video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, + frame_size); + if(!img){ + errorstream<<"generate_image(): Could not create image " + <<"for part_of_name=\""<fill(video::SColor(0,0,0,0)); + + core::dimension2d dim = frame_size; + core::position2d pos_dst(0, 0); + core::position2d pos_src(0, frame_index * frame_size.Y); + baseimg->copyToWithAlpha(img, pos_dst, + core::rect(pos_src, dim), + video::SColor(255,255,255,255), + NULL); + // Replace baseimg + baseimg->drop(); + baseimg = img; + } else { errorstream<<"generate_image(): Invalid " @@ -1527,6 +1725,61 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg, return true; } +void overlay(video::IImage *image, video::IImage *overlay) +{ + /* + Copy overlay to image, taking alpha into account. + Where image is transparent, don't copy from overlay. + Images sizes must be identical. + */ + if(image == NULL || overlay == NULL) + return; + + core::dimension2d dim = image->getDimension(); + core::dimension2d dim_overlay = overlay->getDimension(); + assert(dim == dim_overlay); + + for(u32 y=0; ygetPixel(x,y); + video::SColor c2 = overlay->getPixel(x,y); + u32 a1 = c1.getAlpha(); + u32 a2 = c2.getAlpha(); + if(a1 == 255 && a2 != 0) + { + c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255); + c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255); + c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255); + } + image->setPixel(x,y,c1); + } +} + +/* + Draw an image on top of an another one, using the alpha channel of the + source image + + This exists because IImage::copyToWithAlpha() doesn't seem to always + work. +*/ +static void blit_with_alpha(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size) +{ + for(u32 y0=0; y0getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } +} + void brighten(video::IImage *image) { if(image == NULL) @@ -1545,3 +1798,106 @@ void brighten(video::IImage *image) } } +u32 parseImageTransform(const std::string& s) +{ + int total_transform = 0; + + std::string transform_names[8]; + transform_names[0] = "i"; + transform_names[1] = "r90"; + transform_names[2] = "r180"; + transform_names[3] = "r270"; + transform_names[4] = "fx"; + transform_names[6] = "fy"; + + std::size_t pos = 0; + while(pos < s.size()) + { + int transform = -1; + for(int i = 0; i <= 7; ++i) + { + const std::string &name_i = transform_names[i]; + + if(s[pos] == ('0' + i)) + { + transform = i; + pos++; + break; + } + else if(!(name_i.empty()) && + lowercase(s.substr(pos, name_i.size())) == name_i) + { + transform = i; + pos += name_i.size(); + break; + } + } + if(transform < 0) + break; + + // Multiply total_transform and transform in the group D4 + int new_total = 0; + if(transform < 4) + new_total = (transform + total_transform) % 4; + else + new_total = (transform - total_transform + 8) % 4; + if((transform >= 4) ^ (total_transform >= 4)) + new_total += 4; + + total_transform = new_total; + } + return total_transform; +} + +core::dimension2d imageTransformDimension(u32 transform, core::dimension2d dim) +{ + if(transform % 2 == 0) + return dim; + else + return core::dimension2d(dim.Height, dim.Width); +} + +void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) +{ + if(src == NULL || dst == NULL) + return; + + core::dimension2d srcdim = src->getDimension(); + core::dimension2d dstdim = dst->getDimension(); + + assert(dstdim == imageTransformDimension(transform, srcdim)); + assert(transform >= 0 && transform <= 7); + + /* + Compute the transformation from source coordinates (sx,sy) + to destination coordinates (dx,dy). + */ + int sxn = 0; + int syn = 2; + if(transform == 0) // identity + sxn = 0, syn = 2; // sx = dx, sy = dy + else if(transform == 1) // rotate by 90 degrees ccw + sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx + else if(transform == 2) // rotate by 180 degrees + sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy + else if(transform == 3) // rotate by 270 degrees ccw + sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx + else if(transform == 4) // flip x + sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy + else if(transform == 5) // flip x then rotate by 90 degrees ccw + sxn = 2, syn = 0; // sx = dy, sy = dx + else if(transform == 6) // flip y + sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy + else if(transform == 7) // flip y then rotate by 90 degrees ccw + sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx + + for(u32 dy=0; dygetPixel(sx,sy); + dst->setPixel(dx,dy,c); + } +}