本例使用libpng,将一系列单个的序列帧png文件合并成一个大的png, 去除无用的空白,计算每个系列的最小有效范围。
请看源码吧,libpng用法有不明白的,可以参考一下,有抄到的到方请保留版权
//@author aerror
//2011/12/02
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <tchar.h>
#include <vector>
#include <string>
#include <list>
extern "C"{
#include "png.h"
}
#define EnsureVectorSize(v,ds) {if(v.size()<(unsigned int)ds){ v.resize(ds);}}
typedef struct _RECTANGLE
{
int top;
int left;
int bottom;
int right;
}TRECTANGLE;
class PNG_SRC_FILE
{
public:
std::string filename;
png_bytep bmp;
unsigned int rawWidth ;
unsigned int rawHeight ;
PNG_SRC_FILE(const char * src)
{
filename =src;
bmp = NULL;
}
virtual ~PNG_SRC_FILE()
{
if(bmp!=NULL)
{
delete []bmp;
}
}
};
typedef std::list< PNG_SRC_FILE *> TPNG_SRC_FILE_LIST;
typedef std::list< PNG_SRC_FILE *>::iterator TPNG_SRC_FILE_LIST_ITER;
bool filename_sort(PNG_SRC_FILE * x1,PNG_SRC_FILE * x2)
{
return x1->filename < x2->filename;
}
typedef struct _tanimation_info
{
const char *actionName;
const char *actionDir;
unsigned rowHeight;
unsigned frameWidth;
unsigned frameHeight;
unsigned totalFrame;
unsigned fps;
TPNG_SRC_FILE_LIST * filelist;
unsigned int minTop ;
unsigned int maxBottom ;
unsigned int minLeft ;
unsigned int maxRight ;
}TANIMATION_INFO;
std::vector<BYTE> s_srcDibBitsBuffer;
std::vector<BYTE> s_dstDibBitsBuffer;
std::vector<BYTE> s_dstPngRowsBuffer;
std::vector<BYTE> s_dstPngTempBuffer;
int outputToPngFileNormal(const char *szFileName,unsigned int numDibBtis,const unsigned char *pDibBytes,unsigned int _width, unsigned int _height)
{
FILE *fp;
png_structp png_ptr;
png_infop info_ptr;
/* open the file */
fp = fopen(szFileName, "wb");
if (fp == NULL)
return (ERROR);
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL, NULL, NULL);
if (png_ptr == NULL)
{
fclose(fp);
return (ERROR);
}
/* Allocate/initialize the image information data. REQUIRED */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
fclose(fp);
png_destroy_write_struct(&png_ptr, png_infopp_NULL);
return (ERROR);
}
/* Set error handling. REQUIRED if you aren't supplying your own
* error handling functions in the png_create_write_struct() call.
*/
if (setjmp(png_jmpbuf(png_ptr)))
{
/* If we get here, we had a problem reading the file */
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
return (ERROR);
}
png_init_io(png_ptr, fp);
/* Set the image information here. Width and height are up to 2^31,
* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
* or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
* currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
*/
int src_bytes_per_pixel = numDibBtis/_width/_height;
png_uint_32 k, height, width;
height = _height;
width = _width;
png_set_IHDR(png_ptr, info_ptr, width, height, 8,
PNG_COLOR_TYPE_RGB_ALPHA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_text text_ptr[2]={0,0};
char szDetail[255]="pvr";
/*sprintf(szDetail,"type=%d method=%d size=%dx%d lefttop=%d,%d frame=%dx%d\n",
pInfo->type,
pInfo->method,
(int)(pInfo->width),
(int)(pInfo->height),
(int)(pInfo->left),
(int)(pInfo->top),
(int)(pInfo->frmWidth),
(int)(pInfo->frmHeith) );
*/
text_ptr[0].key = "Author";
text_ptr[0].text = "dany21cn@21cn.com";
text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
text_ptr[1].key = "Description";
text_ptr[1].text = szDetail;
text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png_ptr, info_ptr, text_ptr, 2);
/* Write the file header information. REQUIRED */
png_write_info(png_ptr, info_ptr);
if (height > PNG_UINT_32_MAX/png_sizeof(png_bytep))
png_error (png_ptr, "Image is too tall to process in memory");
png_bytep image = NULL;
png_bytep imagerow = NULL;
EnsureVectorSize(s_dstPngRowsBuffer,width*4);
imagerow = &s_dstPngRowsBuffer[0];
for(int y=0;y<_height;y++)
{
for(int x=0;x<_width;x++)
{
int dx = x;//+pInfo->left;
int dy = y;//+pInfo->top;
if(dx>width || dy>height)
{
printf("invalid offset operation\n");
return ERROR;
}
const unsigned char* psrc = pDibBytes + y*_width*src_bytes_per_pixel+ x*src_bytes_per_pixel;
png_bytep pdst = NULL;
pdst = imagerow +dx*4;
if(src_bytes_per_pixel==4)
{
/*
pdst[0] = psrc[2];
pdst[1] = psrc[1];
pdst[2] = psrc[0];
pdst[3] = psrc[3];
*/
pdst[0] = psrc[0];
pdst[1] = psrc[1];
pdst[2] = psrc[2];
pdst[3] = psrc[3];
}
}
png_write_row(png_ptr,imagerow);
}
/* You can write optional chunks like tEXt, zTXt, and tIME at the end
* as well. Shouldn't be necessary in 1.1.0 and up as all the public
* chunks are supported and you can use png_set_unknown_chunks() to
* register unknown chunks into the info structure to be written out.
*/
/* It is REQUIRED to call this to finish writing the rest of the file */
png_write_end(png_ptr, info_ptr);
/* clean up after the write, and free any memory allocated */
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return 0;
}
TRECTANGLE cutTransparent(png_bytep bmp, unsigned int width, unsigned int height )
{
TRECTANGLE t={0,0,height,width};
//row by row from top
//
for(int y=0;y<height;y++)
{
bool thisLineEmpty = true;
for(int x=0;x<width;x++)
{
unsigned char* psrc = bmp + y*width*4+ x*4;
unsigned int v = *(unsigned int* )psrc;
if(v &0xFF000000)
{
thisLineEmpty = false;
break;
}
}
if(thisLineEmpty)
{
t.top ++ ;
}
else
{
break;
}
}
for(int y=height-1;y>=0;y--)
{
bool thisLineEmpty = true;
for(int x=0;x<width;x++)
{
unsigned char* psrc = bmp + y*width*4+ x*4;
unsigned int v = *(unsigned int* )psrc;
if(v &0xFF000000)
{
thisLineEmpty = false;
break;
}
}
if(thisLineEmpty)
{
t.bottom --;
}
else
{
break;
}
}
for(int x=0;x<width;x++)
{
bool thisLineEmpty = true;
for(int y=0;y<height;y++)
{
unsigned char* psrc = bmp + y*width*4+ x*4;
unsigned int v = *(unsigned int* )psrc;
if(v &0xFF000000)
{
thisLineEmpty = false;
break;
}
}
if(thisLineEmpty)
{
t.left ++ ;
}
else
{
break;
}
}
for(int x=width-1;x>=0;x--)
{
bool thisLineEmpty = true;
for(int y=0;y<height;y++)
{
unsigned char* psrc = bmp + y*width*4+ x*4;
unsigned int v = *(unsigned int* )psrc;
if(v &0xFF000000)
{
thisLineEmpty = false;
break;
}
}
if(thisLineEmpty)
{
t.right --;
}
else
{
break;
}
}
return t;
}
/**
stand=standby
attack=Normal attack
squat=Morale attack
attacked=under attack
defense=????
*/
void concat(const char *packsDir, const char *outputDir)
{
WIN32_FIND_DATAA FindFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
char DirSpec[MAX_PATH]; // directory specification
char DirBase[MAX_PATH]; // directory specification
char filename_dest[MAX_PATH];
char npc_name[MAX_PATH];
DWORD dwError;
TANIMATION_INFO aniInfo[4]={
{ "stand","standby",0,0,0,0,0,0,0,0,0,0},
{ "attack","Normal attack",0,0,0,0,0,0,0,0,0,0},
{ "squat","Morale attack",0,0,0,0,0,0,0,0,0,0},
{ "attacked","under attack",0,0,0,0,0,0,0,0,0,0}
};
strcpy(DirSpec,packsDir);
int rlen = strlen(DirSpec);
if(DirSpec[rlen -1]=='\\')
{
DirSpec[rlen-1]='\0';
}
strcpy(DirBase,DirSpec);
char *last = strrchr(DirBase,'\\');
strcpy(npc_name,last+1);
char szCurPath[MAX_PATH];
GetCurrentDirectoryA(MAX_PATH,szCurPath);
unsigned int currentRowHeight = 0;
unsigned int output_width=0;
unsigned int output_height=0;
for(int n=0;n<4;n++)
{
strcpy(DirSpec,DirBase);
strcat(DirSpec,"\\序列\\");
strcat(DirSpec,aniInfo[n].actionDir);
SetCurrentDirectoryA(DirSpec);
strcat(DirSpec,"\\*.png");
aniInfo[n].filelist = new TPNG_SRC_FILE_LIST();
TPNG_SRC_FILE_LIST &filelist = *aniInfo[n].filelist;
hFind = FindFirstFileA(DirSpec, &FindFileData);
if (hFind != INVALID_HANDLE_VALUE)
{
while (FindNextFileA(hFind, &FindFileData) != 0)
{
filelist.push_back(new PNG_SRC_FILE(FindFileData.cFileName));
}
dwError = GetLastError();
FindClose(hFind);
}
if(filelist.empty())
{
continue;
}
filelist.sort(filename_sort);
//剪切
//
unsigned int minTop =0 ;
unsigned int maxBottom =0 ;
unsigned int minLeft =0 ;
unsigned int maxRight =0 ;
for(TPNG_SRC_FILE_LIST_ITER iter = filelist.begin();
iter !=filelist.end(); iter++)
{
png_structp png_ptr=NULL;
png_infop info_ptr=NULL;
png_infop end_info_ptr=NULL;
/* open the file */
FILE *fp = fopen((*iter)->filename.c_str(), "rb");
if (fp == NULL)
return ;
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL, NULL, NULL);
info_ptr = png_create_info_struct(png_ptr);
end_info_ptr = png_create_info_struct(png_ptr);
png_init_io(png_ptr, fp);
png_read_info(png_ptr,info_ptr);
png_bytep dibbytes = new png_byte[info_ptr->width * info_ptr->height * 4];
(*iter)->bmp = dibbytes ;
(*iter)->rawWidth = info_ptr->width;
(*iter)->rawHeight = info_ptr->height;
for(int y=0;y<info_ptr->height;y++)
{
png_bytep pdest = dibbytes + y*info_ptr->width*4;
png_read_row(png_ptr,pdest,NULL);
}
TRECTANGLE cut = cutTransparent(dibbytes,info_ptr->width,info_ptr->height);
if(minTop==0 || minTop> cut.top)
{
minTop = cut.top;
}
if(minLeft==0 || minLeft> cut.left)
{
minLeft = cut.left;
}
if(maxRight < cut.right)
{
maxRight = cut.right;
}
if(maxBottom < cut.bottom)
{
maxBottom = cut.bottom;
}
png_destroy_read_struct(&png_ptr,&info_ptr,&end_info_ptr);
fclose(fp);
}
aniInfo[n].fps = 10;
aniInfo[n].frameWidth = maxRight - minLeft;
aniInfo[n].frameHeight = maxBottom-minTop;
aniInfo[n].totalFrame= filelist.size();
aniInfo[n].rowHeight = currentRowHeight;
aniInfo[n].minTop =minTop ;
aniInfo[n].maxBottom =maxBottom ;
aniInfo[n].minLeft =minLeft ;
aniInfo[n].maxRight =maxRight ;
currentRowHeight += aniInfo[n].frameHeight;
if(output_width < aniInfo[n].frameWidth * aniInfo[n].totalFrame)
{
output_width = aniInfo[n].frameWidth * aniInfo[n].totalFrame;
}
output_height = currentRowHeight;
}
EnsureVectorSize(s_srcDibBitsBuffer, output_height * output_width * 4);
png_bytep bigmap = &s_srcDibBitsBuffer[0];
currentRowHeight = 0;
std::string xml;
const char *headline_fmt = "<SWFLoader isBattle=\"true\" name=\"%s\" url=\"assets/soldiers/%s.swf\" load=\"false\">\n";
const char *actionline_fmt = " <action name=\"%s\" rowHeight=\"%d\" frameWidth=\"%d\" frameHeight=\"%d\" fps=\"120\" totalFrame=\"%d\" attackFrame=\"0\" attackedFrame=\"0\" isRight=\"true\" offsetX=\"115\" offsetY=\"-268\"/>\n";
const char *endline_fmt = "</SWFLoader>\n";
sprintf(DirSpec,headline_fmt,npc_name,npc_name);
xml = DirSpec;
for(int n=0;n<4;n++)
{
TPNG_SRC_FILE_LIST &filelist = *aniInfo[n].filelist;
if(filelist.empty())
{
continue;
}
sprintf(DirSpec,actionline_fmt,aniInfo[n].actionName,aniInfo[n].rowHeight,aniInfo[n].frameWidth,aniInfo[n].frameHeight,aniInfo[n].totalFrame);
xml +=DirSpec;
int curCol= 0;
for(TPNG_SRC_FILE_LIST_ITER iter = filelist.begin();
iter !=filelist.end(); iter++)
{
int yy = 0;
for(int y=aniInfo[n].minTop;y<aniInfo[n].maxBottom;y++)
{
int xx = 0;
for(int x=aniInfo[n].minLeft;x<aniInfo[n].maxRight;x++)
{
unsigned int* psrc = (unsigned int*)((*iter)->bmp + y*(*iter)->rawWidth *4+ x*4);
int dx = curCol * aniInfo[n].frameWidth + xx;
int dy = currentRowHeight +yy;
unsigned int* dest = (unsigned int*)(bigmap + dy * output_width * 4 + dx*4);
xx++;
*dest = *psrc;
}
yy ++;
}
curCol ++;
PNG_SRC_FILE * f = *iter;
delete f;
}
delete aniInfo[n].filelist;
currentRowHeight += aniInfo[n].frameHeight;
}
xml += endline_fmt;
SetCurrentDirectoryA(szCurPath);
strcpy(filename_dest,outputDir);
if(outputDir[strlen(outputDir)-1]!='\\')
strcat(filename_dest,"\\");
strcat(filename_dest,npc_name);
strcat(filename_dest,".png");
outputToPngFileNormal(filename_dest,4*output_width * output_height, &s_srcDibBitsBuffer[0],output_width,output_height);
strcpy(filename_dest,outputDir);
if(outputDir[strlen(outputDir)-1]!='\\')
strcat(filename_dest,"\\");
strcat(filename_dest,"AnimationConfig.xml");
FILE *xmlFile = fopen(filename_dest,"a");
if(xmlFile==NULL)
{
return ;
}
fwrite(xml.c_str(),1,xml.length(),xmlFile);
fclose(xmlFile);
}
void print_uasge()
{
printf("USEAGE:\n"
" pngconcat x:\\path\\to\\yournpcdirectory x:\\path\\to\\output \n"
"EXAMPLE:\n"
" pngconcat D:\\hero_xxxxxx D:\\temp \n"
);
}
int checkDir(const char * szdir)
{
struct _stat buf;
int result;
char timebuf[26];
const char* filename = szdir;
errno_t err;
// Get data associated with "crt_stat.c":
result = _stat( filename, &buf );
// Check if statistics are valid:
if( result != 0 )
{
perror( "Problem getting information" );
switch (errno)
{
case ENOENT:
printf("File %s not found.\n", filename);
break;
case EINVAL:
printf("Invalid parameter to _stat.\n");
break;
default:
/* Should never be reached. */
printf("Unexpected error in _stat.\n");
}
return -1;
}
else
{
if( (buf.st_mode & _S_IFDIR)==0)
{
printf("input %s is not a directory.\n", filename);
return -1;
}
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
if(argc != 3 || checkDir(argv[1]) || checkDir(argv[2]) )
{
print_uasge();
return -1;
}
concat(argv[1],argv[2]);
return 0;
}