etqw-sdk/source/idlib/ImagePacker.cpp

343 lines
9.3 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "precompiled.h"
#pragma hdrstop
#include "ImagePacker.h"
class sdImagePackerNode {
public:
sdImagePackerNode () {
children[0] = children[1] = NULL;
// imageIndex = -1;
isStuffed = false;
}
sdImagePackerNode ( const sdImagePackerNode &other ) {
rect = other.rect;
isStuffed = other.isStuffed;
if ( other.children[0] ) {
children[0] = new sdImagePackerNode( *other.children[0] );
} else {
children[0] = NULL;
}
if ( other.children[1] ) {
children[1] = new sdImagePackerNode( *other.children[1] );
} else {
children[1] = NULL;
}
}
~sdImagePackerNode () {
delete children[0];
delete children[1];
}
sdImagePackerNode *Insert( const sdSubImage &image );
void StuffUnderRect( const sdSubImage &image );
sdImagePackerNode *children[2];
sdSubImage rect;
//int imageIndex;
bool isStuffed;
};
sdImagePackerNode* sdImagePackerNode::Insert( const sdSubImage &image ) {
if ( children[0] ) {
sdImagePackerNode*newNode = children[0]->Insert( image );
if ( newNode ) return newNode;
return children[1]->Insert( image );
} else {
// If this leaf is already filled image data we can't do anything here
if ( isStuffed ) {
return NULL;
}
// If there is not enough space we will expand at the highest level by doubling the image so just
// return null for now
if ( ( rect.width < image.width ) || ( rect.height < image.height ) ) {
// Try again with flipped image dimensions
if ( image.x >= 0 /* && tryFlipping */ ) {
sdSubImage flip;
flip.x = -1;
flip.y = -1;
flip.width = image.height;
flip.height = image.width;
return Insert( flip );
} else {
return NULL;
}
}
// Exact match
if ( ( rect.width == image.width ) && ( rect.height == image.height ) ) {
isStuffed = true;
return this;
}
// Split
children[0] = new sdImagePackerNode;
children[1] = new sdImagePackerNode;
// Slit along the axis that will leave the biggest continuous part, with a preference for horizontal splits if equal
int dw = rect.width - image.width;
int dh = rect.height - image.height;
if ( dw > dh ) {
// Vertical split
children[0]->rect.x = rect.x;
children[0]->rect.y = rect.y;
children[0]->rect.width = image.width;
children[0]->rect.height = rect.height;
children[1]->rect.x = rect.x + image.width;
children[1]->rect.y = rect.y;
children[1]->rect.width = rect.width - image.width;
children[1]->rect.height = rect.height;
} else {
// Horizontal split
children[0]->rect.x = rect.x;
children[0]->rect.y = rect.y;
children[0]->rect.width = rect.width;
children[0]->rect.height = image.height;
children[1]->rect.x = rect.x;
children[1]->rect.y = rect.y + image.height;
children[1]->rect.width = rect.width;
children[1]->rect.height = rect.height - image.height;
}
// children[0] is the best fitting now so insert it there
return children[0]->Insert( image );
}
}
void sdImagePackerNode::StuffUnderRect( const sdSubImage &image ) {
if ( !rect.Overlaps( image ) ) {
return;
}
if ( !children[0] ) {
// stuff leaves
isStuffed = true;
} else {
children[0]->StuffUnderRect( image );
children[1]->StuffUnderRect( image );
}
}
sdImagePacker::sdImagePacker( int width, int height ) {
usedWidth = usedHeight = 0;
root = new sdImagePackerNode;
root->rect.x = 0;
root->rect.width = idMath::CeilPowerOfTwo( width );
root->rect.y = 0;
root->rect.height = idMath::CeilPowerOfTwo( height );
}
sdImagePacker::~sdImagePacker( ) {
usedWidth = usedHeight = 0;
delete root;
}
sdImagePacker::sdImagePacker( const sdImagePacker &other ) {
root = new sdImagePackerNode( *other.root );
usedWidth = other.usedWidth;
usedHeight = other.usedHeight;
}
sdImagePacker &sdImagePacker::operator= ( const sdImagePacker &other ) {
delete root;
root = new sdImagePackerNode( *other.root );
usedWidth = other.usedWidth;
usedHeight = other.usedHeight;
return *this;
}
sdSubImage sdImagePacker::PackImage( int width, int height, bool expandIfFull ) {
// First time, create a root node
if ( !root ) {
root = new sdImagePackerNode;
root->rect.x = 0;
root->rect.width = idMath::CeilPowerOfTwo( width );
root->rect.y = 0;
root->rect.height = idMath::CeilPowerOfTwo( height );
}
// Insert and optionally enlarge the image
sdSubImage arg;
arg.x = arg.y = 0;
arg.width = width;
arg.height = height;
sdImagePackerNode *resultNode = root->Insert( arg );
if ( !resultNode ) {
// Merge the borders as a last effort and try again
sdImagePackerNode *newRoot = new sdImagePackerNode;
sdImagePackerNode *newChild1 = new sdImagePackerNode;
newRoot->rect = root->rect;
newChild1->rect = root->rect;
int dw = root->rect.width - usedWidth;
int dh = root->rect.height - usedHeight;
if ( dh > dw ) {
newChild1->rect.y = usedHeight;
newChild1->rect.height = newRoot->rect.height - usedHeight;
root->StuffUnderRect( newChild1->rect ); // Dont let the holes in root use this...
root->rect.height = usedHeight;
newRoot->children[0] = root;
newRoot->children[1] = newChild1;
root = newRoot;
// common->Printf( "Recovered %i x %i block\n", newChild1->rect.width, newChild1->rect.height );
} else {
newChild1->rect.x = usedWidth;
newChild1->rect.width = newRoot->rect.width - usedWidth;
root->StuffUnderRect( newChild1->rect ); // Dont let the holes in root use this...
root->rect.width = usedWidth;
newRoot->children[0] = root;
newRoot->children[1] = newChild1;
root = newRoot;
// common->Printf( "Recovered %i x %i block\n", newChild1->rect.width, newChild1->rect.height );
}
resultNode = root->Insert( arg );
}
// Expand if needed
while ( expandIfFull && !resultNode ) {
bool isHorizontal = false;
// Is the topmost split a horizontal or a vertical one?
if ( root->children[0] && ( root->children[0]->rect.x == root->children[1]->rect.x ) ) {
isHorizontal = true;
}
if ( isHorizontal ) {
// double with and make root a vertical split
sdImagePackerNode *newRoot = new sdImagePackerNode;
sdImagePackerNode *newChild1 = new sdImagePackerNode;
newRoot->rect = root->rect;
newRoot->rect.width *= 2;
newChild1->rect = root->rect;
newChild1->rect.x = root->rect.x + root->rect.width;
newRoot->children[0] = root;
newRoot->children[1] = newChild1;
root = newRoot;
} else {
// double height and make root a horizontal split
sdImagePackerNode *newRoot = new sdImagePackerNode;
sdImagePackerNode *newChild1 = new sdImagePackerNode;
newRoot->rect = root->rect;
newRoot->rect.height *= 2;
newChild1->rect = root->rect;
newChild1->rect.y = root->rect.y + root->rect.height;
newRoot->children[0] = root;
newRoot->children[1] = newChild1;
root = newRoot;
}
// Try inserting in the new expanded tree
resultNode = root->Insert( arg );
}
// Return the inserted rect or the empty rect if no suitable location was found
sdSubImage result;
if ( resultNode ) {
result = resultNode->rect;
if ( (result.x + result.width) > usedWidth ) {
usedWidth = result.x + result.width;
}
if ( (result.y + result.height) > usedHeight ) {
usedHeight = result.y + result.height;
}
} else {
result.x = result.y = result.width = result.height = 0;
}
return result;
}
static void DrawRect( byte *data, int width, int height, sdSubImage rect ) {
byte *pixel;
int x2 = rect.x + rect.width - 1;
int y2 = rect.y + rect.height - 1;
for ( int i=rect.x; i<=x2; i++ ) {
pixel = data + ( rect.y*width+i )*4;
pixel[0] = 255; pixel[1] = pixel[2] = pixel[3] = 0;
pixel = data + ( y2*width+i )*4;
pixel[0] = 255; pixel[1] = pixel[2] = pixel[3] = 0;
}
for ( int j=rect.y; j<=y2; j++ ) {
pixel = data + ( j*width+rect.x )*4;
pixel[0] = 255; pixel[1] = pixel[2] = pixel[3] = 0;
pixel = data + ( j*width+x2 )*4;
pixel[0] = 255; pixel[1] = pixel[2] = pixel[3] = 0;
}
}
static void CheckerRect( byte *data, int width, int height, sdSubImage rect ) {
byte *pixel;
int x2 = rect.x + rect.width - 1;
int y2 = rect.y + rect.height - 1;
for ( int i=rect.x; i<=x2; i++ ) {
for ( int j=rect.y; j<=y2; j++ ) {
pixel = data + ( j*width+i )*4;
if ( ((j/5) + (i/5)) & 1 ) {
pixel[2] = 255; pixel[0] = pixel[1] = pixel[3] = 0;
}
}
}
}
void sdImagePacker::DrawTree( byte *image, int width, int height ) {
assert( width >= GetWidth() );
assert( height >= GetHeight() );
DrawTree_R( root, image, width, height );
}
void sdImagePacker::DrawTree_R( sdImagePackerNode *node, byte *image, int width, int height ) {
if ( node->isStuffed ) CheckerRect( image, width, height, node->rect );
DrawRect( image, width, height, node->rect );
if ( node->children[0] ) DrawTree_R( node->children[0], image, width, height );
if ( node->children[1] ) DrawTree_R( node->children[1], image, width, height );
}
sdSubImage sdImagePacker::PackImage( sdSubImage &image ) {
return PackImage( image.width, image.height );
}
int sdImagePacker::GetWidth( void ) {
if ( !root ) return 0;
return root->rect.width;
}
int sdImagePacker::GetHeight( void ) {
if ( !root ) return 0;
return root->rect.height;
}