/*
-Minetest-c55
-Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#include "debug.h"
#include "main.h" // for g_settings
#include "filesys.h"
-#include "utility.h"
#include "settings.h"
#include "mesh.h"
#include <ICameraSceneNode.h>
#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
"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);
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);
}
{
assert(img);
// Remove old image
- core::map<std::string, video::IImage*>::Node *n;
+ std::map<std::string, video::IImage*>::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();
}
}
video::IImage* get(const std::string &name)
{
- core::map<std::string, video::IImage*>::Node *n;
+ std::map<std::string, video::IImage*>::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<std::string, video::IImage*>::Node *n;
+ std::map<std::string, video::IImage*>::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());
return img;
}
private:
- core::map<std::string, video::IImage*> m_images;
+ std::map<std::string, video::IImage*> m_images;
};
/*
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").
*/
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.
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()
{
// 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.
// This should be only accessed from the main thread
SourceImageCache m_sourcecache;
+ // Thread-safe cache of what source images are known (true = known)
+ MutexedMap<std::string, bool> m_source_image_existence;
+
// A texture id is index in this array.
// The first position contains a NULL texture.
- core::array<SourceAtlasPointer> m_atlaspointer_cache;
+ std::vector<SourceAtlasPointer> m_atlaspointer_cache;
// Maps a texture name to an index in the former.
- core::map<std::string, u32> m_name_to_id;
+ std::map<std::string, u32> m_name_to_id;
// The two former containers are behind this mutex
JMutex m_atlaspointer_cache_mutex;
See if texture already exists
*/
JMutexAutoLock lock(m_atlaspointer_cache_mutex);
- core::map<std::string, u32>::Node *n;
+ std::map<std::string, u32>::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;
}
}
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<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> 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".
{
JMutexAutoLock lock(m_atlaspointer_cache_mutex);
- core::map<std::string, u32>::Node *n;
+ std::map<std::string, u32>::iterator n;
n = m_name_to_id.find(name);
- if(n != NULL)
+ if(n != m_name_to_id.end())
{
/*infostream<<"getTextureIdDirect(): \""<<name
<<"\" found in cache"<<std::endl;*/
- return n->getValue();
+ return n->second;
}
}
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="<<id<<" for name \""<<name<<"\""<<std::endl;*/
/*
Fetch textures
*/
- if(m_get_texture_queue.size() > 0)
+ if(!m_get_texture_queue.empty())
{
GetRequest<std::string, u32, u8, u8>
request = m_get_texture_queue.pop();
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()
JMutexAutoLock lock(m_atlaspointer_cache_mutex);
// Create an image of the right size
- core::dimension2d<u32> atlas_dim(1024,1024);
+ core::dimension2d<u32> max_dim = driver->getMaxTextureSize();
+ core::dimension2d<u32> 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);
main content features
*/
- core::map<std::string, bool> sourcelist;
+ std::set<std::string> sourcelist;
for(u16 j=0; j<MAX_CONTENT+1; j++)
{
const ContentFeatures &f = ndef->get(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<MINERAL_COUNT; k++){
- std::string mineraltexture = mineral_block_texture(k);
- std::string fulltexture = name + "^" + mineraltexture;
- sourcelist[fulltexture] = true;
- }
- }
+ std::string name = f.tiledef[i].name;
+ sourcelist.insert(name);
}
}
infostream<<"Creating texture atlas out of textures: ";
- for(core::map<std::string, bool>::Iterator
- i = sourcelist.getIterator();
- i.atEnd() == false; i++)
+ for(std::set<std::string>::iterator
+ i = sourcelist.begin();
+ i != sourcelist.end(); ++i)
{
- std::string name = i.getNode()->getKey();
+ std::string name = *i;
infostream<<"\""<<name<<"\" ";
}
infostream<<std::endl;
// Padding to disallow texture bleeding
+ // (16 needed if mipmapping is used; otherwise less will work too)
s32 padding = 16;
-
- s32 column_width = 256;
s32 column_padding = 16;
+ s32 column_width = 256; // Space for 16 pieces of 16x16 textures
/*
First pass: generate almost everything
*/
core::position2d<s32> pos_in_atlas(0,0);
+ pos_in_atlas.X = column_padding;
pos_in_atlas.Y = padding;
- for(core::map<std::string, bool>::Iterator
- i = sourcelist.getIterator();
- i.atEnd() == false; i++)
+ for(std::set<std::string>::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,
core::dimension2d<u32> dim = img2->getDimension();
- // Don't add to atlas if image is large
- core::dimension2d<u32> max_size_in_atlas(32,32);
+ // Don't add to atlas if image is too large
+ core::dimension2d<u32> max_size_in_atlas(64,64);
if(dim.Width > max_size_in_atlas.Width
|| dim.Height > max_size_in_atlas.Height)
{
// 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."
<<std::endl;
break;
}
pos_in_atlas.Y = padding;
- pos_in_atlas.X += column_width + column_padding;
+ pos_in_atlas.X += column_width + column_padding*2;
}
/*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
atlas_img->setPixel(x,dst_y,c);
}
+ for(u32 side=0; side<2; side++) // left and right
+ for(s32 x0=0; x0<column_padding; x0++)
+ for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
+ {
+ s32 dst_x;
+ s32 src_x;
+ if(side==0)
+ {
+ dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
+ src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
+ }
+ else
+ {
+ dst_x = -x0 + pos_in_atlas.X-1;
+ src_x = pos_in_atlas.X;
+ }
+ s32 y = y0 + pos_in_atlas.Y;
+ s32 src_y = MYMAX((int)pos_in_atlas.Y, MYMIN((int)pos_in_atlas.Y + (int)dim.Height - 1, y));
+ s32 dst_y = y;
+ video::SColor c = atlas_img->getPixel(src_x, src_y);
+ atlas_img->setPixel(dst_x,dst_y,c);
+ }
+
img2->drop();
/*
bool reuse_old_id = false;
u32 id = m_atlaspointer_cache.size();
// Check old id without fetching a texture
- core::map<std::string, u32>::Node *n;
+ std::map<std::string, u32>::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"<<std::endl;*/
/*
Second pass: set texture pointer in generated AtlasPointers
*/
- for(core::map<std::string, bool>::Iterator
- i = sourcelist.getIterator();
- i.atEnd() == false; i++)
+ for(std::set<std::string>::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 "<<name<<" is "<<id<<std::endl;
/*
Write image to file so that it can be inspected
*/
- /*std::string atlaspath = porting::path_userdata
+ /*std::string atlaspath = porting::path_user
+ DIR_DELIM + "generated_texture_atlas.png";
infostream<<"Removing and writing texture atlas for inspection to "
<<atlaspath<<std::endl;
char separator = '^';
// Find last meta separator in name
- s32 last_separator_position = -1;
- for(s32 i=name.size()-1; i>=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="<<last_separator_position
// Position to copy the blitted from in the blitted image
core::position2d<s32> pos_from(0,0);
// Blit
- image->copyToWithAlpha(baseimg, pos_to,
+ /*image->copyToWithAlpha(baseimg, pos_to,
core::rect<s32>(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();
}
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<u32> dim_base = baseimg->getDimension();
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<u32> 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<u32> dim_crack_scaled_single(
- dim_base.Width,
- dim_base.Height
- );
- // Dimension of scaled size
- core::dimension2d<u32> dim_crack_scaled(
- dim_crack_scaled_single.Width,
- dim_crack_scaled_single.Height * crack_count
+ // Dimension of a single crack stage
+ core::dimension2d<u32> 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<s32>(pos_crack, dim_crack_cropped));
// Scale crack image by copying
- img_crack->copyToScaling(img_crack_scaled);
-
- // Position to copy the crack from
- core::position2d<s32> pos_crack_scaled(
- 0,
- dim_crack_scaled_single.Height * progression
- );
-
- // This tiling does nothing currently but is useful
- for(u32 y0=0; y0<dim_base.Height
- / dim_crack_scaled_single.Height; y0++)
- for(u32 x0=0; x0<dim_base.Width
- / dim_crack_scaled_single.Width; x0++)
+ img_crack_cropped->copyToScaling(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<s32> 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<s32> 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<s32>(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();
}
u32 h0 = stoi(sf.next(":"));
infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
core::dimension2d<u32> dim(w0,h0);
- baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
+ if(baseimg == NULL)
+ {
+ baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
+ baseimg->fill(video::SColor(0,0,0,0));
+ }
while(sf.atend() == false)
{
u32 x = stoi(sf.next(","));
driver->createImage(video::ECF_A8R8G8B8, dim);
img->copyTo(img2);
img->drop();
- img2->copyToWithAlpha(baseimg, pos_base,
+ /*img2->copyToWithAlpha(baseimg, pos_base,
core::rect<s32>(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
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=\""<<part_of_name
+ <<"\", cancelling."<<std::endl;
+ return false;
+ }
+
+ u32 transform = parseImageTransform(part_of_name.substr(10));
+ core::dimension2d<u32> 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 &.
if(rtt == NULL)
{
- errorstream<<"generate_image(): render to texture failed."
- " Creating fallback image"<<std::endl;
baseimg = generate_image_from_scratch(
imagename_top, device, sourcecache);
return true;
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 "<<percent<<"%% of "<<filename<<std::endl;
+
+ if(baseimg == NULL)
+ baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
+ video::IImage *img = sourcecache->getOrLoad(filename, device);
+ if(img)
+ {
+ core::dimension2d<u32> dim = img->getDimension();
+ core::position2d<s32> pos_base(0, 0);
+ video::IImage *img2 =
+ driver->createImage(video::ECF_A8R8G8B8, dim);
+ img->copyTo(img2);
+ img->drop();
+ core::position2d<s32> clippos(0, 0);
+ clippos.Y = dim.Height * (100-percent) / 100;
+ core::dimension2d<u32> clipdim = dim;
+ clipdim.Height = clipdim.Height * percent / 100 + 1;
+ core::rect<s32> cliprect(clippos, clipdim);
+ img2->copyToWithAlpha(baseimg, pos_base,
+ core::rect<s32>(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=\""<<part_of_name
+ <<"\", cancelling."<<std::endl;
+ return false;
+ }
+
+ v2u32 frame_size = baseimg->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=\""<<part_of_name
+ <<"\", cancelling."<<std::endl;
+ return false;
+ }
+
+ // Fill target image with transparency
+ img->fill(video::SColor(0,0,0,0));
+
+ core::dimension2d<u32> dim = frame_size;
+ core::position2d<s32> pos_dst(0, 0);
+ core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
+ baseimg->copyToWithAlpha(img, pos_dst,
+ core::rect<s32>(pos_src, dim),
+ video::SColor(255,255,255,255),
+ NULL);
+ // Replace baseimg
+ baseimg->drop();
+ baseimg = img;
+ }
else
{
errorstream<<"generate_image(): Invalid "
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<u32> dim = image->getDimension();
+ core::dimension2d<u32> dim_overlay = overlay->getDimension();
+ assert(dim == dim_overlay);
+
+ for(u32 y=0; y<dim.Height; y++)
+ for(u32 x=0; x<dim.Width; x++)
+ {
+ video::SColor c1 = image->getPixel(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; y0<size.Y; y0++)
+ for(u32 x0=0; x0<size.X; x0++)
+ {
+ s32 src_x = src_pos.X + x0;
+ s32 src_y = src_pos.Y + y0;
+ s32 dst_x = dst_pos.X + x0;
+ s32 dst_y = dst_pos.Y + y0;
+ video::SColor src_c = src->getPixel(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)
}
}
+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<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
+{
+ if(transform % 2 == 0)
+ return dim;
+ else
+ return core::dimension2d<u32>(dim.Height, dim.Width);
+}
+
+void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
+{
+ if(src == NULL || dst == NULL)
+ return;
+
+ core::dimension2d<u32> srcdim = src->getDimension();
+ core::dimension2d<u32> 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; dy<dstdim.Height; dy++)
+ for(u32 dx=0; dx<dstdim.Width; dx++)
+ {
+ u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
+ u32 sx = entries[sxn];
+ u32 sy = entries[syn];
+ video::SColor c = src->getPixel(sx,sy);
+ dst->setPixel(dx,dy,c);
+ }
+}