装箱问题,是个NP问题。至于装箱问题到底是个什么东西,可以看看百度文档http://wenku.baidu.com/view/f6e7f80590c69ec3d5bb755f.html。其实我没看。
研究二维矩形装箱问题,是因为需要将小图拼成大图,作为一个大的texture加载到内存内,从而实现减少内存消耗的目的。
做游戏的都知道texturepacker工具,一款付费软件,有无限多个破解安装程序。
texturepacker就是将小图合成为一张大图,导出png图像,以及含有小图大小,位置信息的plist文本文件。
给布置的作业,其实是让用设计模式完成一款texturepacker类工具,可惜编程能力不强,另一方面比较懒,不想去做可视化界面,于是就只找了篇硕士论文,按照论文内提到的一种算法,写了写程序。
论文是:二维矩形条带装箱问题的底部左齐择优匹配算法。不知道原作者是高兴还是忧伤我酱紫给他做广告。
程序运行环境为:cocos2dx 3.0, vs2012.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #ifndef __HELLOWORLD_SCENE_H__ 2 #define __HELLOWORLD_SCENE_H__ 3 4 #include "cocos2d.h" 5 #include "BigImage.h" 6 #include "BaseObject.h" 7 #include "LLABF_Algorithm.h" 8 9 class BigImage; 10 class HelloWorld : public cocos2d::Layer 11 { 12 public: 13 ~HelloWorld(); 14 // there's no 'id' in cpp, so we recommend returning the class instance pointer 15 static cocos2d::Scene* createScene(); 16 17 // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone 18 virtual bool init(); 19 20 // a selector callback 21 void menuCloseCallback(cocos2d::Ref* pSender); 22 23 void initSimpleImageInfo( SimpleImageInfo * simpleImage ); 24 25 // implement the "static create()" method manually 26 CREATE_FUNC(HelloWorld); 27 28 std::vector<SimpleImageInfo*> imageInfos; 29 30 // 图像像素拷贝 将一张图的内容复制到另一张大图中 31 void copyPixels(); 32 33 BigImage * m_bigImage; 34 void initTestImageInfos(); 35 void initTestDictionary(); 36 }; 37 38 #endif // __HELLOWORLD_SCENE_H__
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "HelloWorldScene.h" 2 #include "cocos2d/external/png/include/win32/png.h" 3 #include <math.h> 4 #include <iostream> 5 #include <fstream> 6 #include <string> 7 8 using namespace std; 9 10 USING_NS_CC; 11 12 Scene* HelloWorld::createScene() 13 { 14 auto scene = Scene::create(); 15 auto layer = HelloWorld::create(); 16 scene->addChild(layer); 17 return scene; 18 } 19 20 bool HelloWorld::init() 21 { 22 if ( !Layer::init() ) 23 { 24 return false; 25 } 26 27 Size visibleSize = Director::getInstance()->getVisibleSize(); 28 Point origin = Director::getInstance()->getVisibleOrigin(); 29 auto closeItem = MenuItemImage::create( 30 "CloseNormal.png", 31 "CloseSelected.png", 32 CC_CALLBACK_1(HelloWorld::menuCloseCallback, this)); 33 34 closeItem->setPosition(Point(origin.x + visibleSize.width - closeItem->getContentSize().width/2 , 35 origin.y + closeItem->getContentSize().height/2)); 36 auto menu = Menu::create(closeItem, NULL); 37 menu->setPosition(Point::ZERO); 38 this->addChild(menu, 1); 39 auto label = LabelTTF::create("Hello World", "Arial", 24); 40 41 label->setPosition(Point(origin.x + visibleSize.width/2, 42 origin.y + visibleSize.height - label->getContentSize().height)); 43 44 return true; 45 } 46 47 48 void HelloWorld::menuCloseCallback(Ref* pSender) 49 { 50 #if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) 51 MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert"); 52 return; 53 #endif 54 imageInfos.clear(); 55 system("dir /b *.png > untitled.plist"); 56 int bigImageWidth = 512; 57 string fullPath = FileUtils::getInstance()->fullPathForFilename("untitled.plist"); 58 string filecontent = FileUtils::getInstance()->getStringFromFile(fullPath); 59 ifstream fp( fullPath ); 60 if (!fp) 61 { 62 cerr << "OPEN ERROR" << endl; 63 return ; 64 } 65 66 if ( fp ) 67 { 68 string s; 69 while (getline(fp,s)) 70 { 71 SimpleImageInfo * image = new SimpleImageInfo(); 72 image->imageName = s; 73 imageInfos.push_back(image); 74 CCLog( s.c_str()) ; 75 } 76 fp.close(); 77 78 // 初始化I 79 for ( int i = 0; i< imageInfos.size(); i++ ) 80 { 81 initSimpleImageInfo(imageInfos[i]); 82 imageInfos[i]->id = i; 83 } 84 LLABF * llabf = new LLABF(); 85 llabf->initImages(imageInfos, bigImageWidth ); 86 llabf->runLLABF(); 87 88 89 90 m_bigImage = new BigImage("my_test_1.png",bigImageWidth,llabf->getBigImageHeight(),"my_test_1.plist"); 91 m_bigImage->initBigImageWithImagesData(&imageInfos); 92 m_bigImage->publish(); 93 } 94 95 CCLog("end"); 96 97 //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("my_test_1.plist"); 98 //auto sprite = Sprite::createWithSpriteFrameName("a1.png"); 99 //sprite->setPosition( 300, 300); 100 //this->addChild(sprite, 1000); 101 102 #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) 103 exit(0); 104 #endif 105 } 106 107 void HelloWorld::initSimpleImageInfo( SimpleImageInfo * simpleImage ) 108 { 109 // 读取png文件头 110 std::string fullPath_png = FileUtils::getInstance()->fullPathForFilename( simpleImage->imageName ); 111 unsigned char header[8]; 112 int width, height; 113 png_byte color_type; //图片到类型(可能会用在是否是开启来通道) 114 png_byte bit_depth; //字节深度 115 116 png_structp png_ptr; // 图片 117 png_infop info_ptr; // 图片的信息 118 int number_of_passes;// 隔行扫描 119 png_bytep * row_pointers;// 图片的数据内容 120 int row, col, pos; // 用于改变png像素排列的问题 121 GLubyte * rgba; 122 123 FILE * fp = fopen( fullPath_png.c_str(), "rb"); 124 if ( !fp ) 125 { 126 fclose(fp); 127 return; 128 } 129 fread(header, 1, 8, fp); 130 if ( png_sig_cmp(header, 0, 8 )) // 读取文件头判断是否是png图片,不是则做出相应处理 131 { 132 fclose(fp); 133 return; 134 } 135 //根据libpng的libpng-manual.txt的说明使用文档 接下来必须初始化png_structp 和 png_infop 136 png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL); //后三个是绑定错误以及警告的函数这里设置为空 137 138 if(!png_ptr) 139 { 140 fclose(fp); 141 return; 142 } 143 //根据初始化的png_ptr初始化png_infop 144 info_ptr=png_create_info_struct(png_ptr); 145 if(!info_ptr) 146 { 147 //初始化失败以后销毁png_structp 148 png_destroy_read_struct(&png_ptr,(png_infopp)NULL,(png_infopp)NULL); 149 fclose(fp); 150 return ; 151 } 152 //老老实实按照libpng给到的说明稳定步骤来 错误处理! 153 if (setjmp(png_jmpbuf(png_ptr))) 154 { 155 //释放占用的内存!然后关闭文件返回一个贴图ID此处应该调用一个生成默认贴图返回ID的函数 156 157 png_destroy_read_struct(&png_ptr,(png_infopp)NULL,(png_infopp)NULL); 158 159 fclose(fp); 160 161 return ; 162 163 } 164 //你需要确保是通过2进制打开的文件。通过i/o定制函数png_init_io 165 png_init_io(png_ptr,fp); 166 //似乎是说要告诉libpng文件从第几个开始missing 167 png_set_sig_bytes(png_ptr, 8); 168 //如果你只想简单的操作你现在可以实际读取图片信息了! 169 png_read_info(png_ptr, info_ptr); 170 //获得图片到信息 width height 颜色类型 字节深度 171 width = png_get_image_width(png_ptr, info_ptr); 172 height = png_get_image_height(png_ptr, info_ptr); 173 color_type = png_get_color_type(png_ptr, info_ptr); 174 //如果图片带有alpha通道就需要 175 // if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) 176 177 // png_set_swap_alpha(png_ptr); 178 bit_depth = png_get_bit_depth(png_ptr, info_ptr); 179 180 fclose(fp); 181 182 simpleImage->width = width; 183 simpleImage->height = height; 184 } 185 186 void HelloWorld::initTestImageInfos() 187 { 188 SimpleImageInfo * image1 = new SimpleImageInfo(); 189 image1->imageName = "a5.png"; 190 image1->x = 0; 191 image1->y = 0; 192 image1->width = 576; 193 image1->height = 154; 194 image1->isRotate = true; 195 imageInfos.push_back(image1); 196 197 SimpleImageInfo * image2 = new SimpleImageInfo(); 198 image2->imageName = "a6.png"; 199 image2->x = 154; 200 image2->y = 0; 201 image2->width = 576; 202 image2->height = 154; 203 image2->isRotate = true; 204 imageInfos.push_back(image2); 205 } 206 207 HelloWorld::~HelloWorld() 208 { 209 for ( int i = 0; i< imageInfos.size(); i++ ) 210 { 211 delete imageInfos[i]; 212 } 213 imageInfos.clear(); 214 } 215 216 void HelloWorld::initTestDictionary() 217 { 218 Dictionary * dict = Dictionary::create(); 219 auto string = String::create("test_key"); 220 dict->setObject(string, "string element key"); // 添加一个键值对 221 auto doubleObject = Double::create(1024.123); 222 dict->setObject(doubleObject, "double"); 223 224 Dictionary * dict_1 = Dictionary::create(); 225 dict_1->setObject(String::create("string in dictInDict value"), "string in dictInDict key"); 226 dict->setObject(dict_1, "dict_test_key"); 227 228 std::string writablePath = FileUtils::getInstance()->getWritablePath(); 229 std::string fullPath = writablePath + "text.plist"; 230 if(dict->writeToFile(fullPath.c_str())) 231 log("see the plist file at %s", fullPath.c_str()); 232 else 233 log("write plist file failed"); 234 }
其中用到了system函数,将Resource目录内的png图像导入untitled.plist文件内。其实这个应该做个UI,弹出文件夹选择框,多选文件,将文件名导入。我太懒了,不想去看MFC或者QT。就这样凑合吧。
HelloWorldScene主要就是导出文件夹内的png文件名。然后初始化一些小图信息,转给LLABF算法,最后BigImage类根据LLABF算法计算得到的小图位置信息,生成大图png,以及plist。就这么简单。
下面就是LLABF算法。应该差不多是按照论文内描述的那样写的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #ifndef __LLABF_ALGORITHM_H_ 2 #define __LLABF_ALGORITHM_H_ 3 #include "BaseObject.h" 4 #include <iostream> 5 #include <vector> 6 7 class LLABF 8 { 9 public: 10 LLABF(); 11 ~LLABF(); 12 13 void initImages( std::vector<SimpleImageInfo*> & images , int bigImageWidth ); 14 void runLLABF(); 15 16 int FullFitFirst( SimpleEdgeInfo* edge ); // 完全匹配优先 17 int WidthFitFirst( SimpleEdgeInfo* edge ); // 宽度匹配优先 18 int HeightFitFirst( SimpleEdgeInfo* edge ); // 高度匹配优先 19 int JointWidthFitFirst( SimpleEdgeInfo* edge ); // 组合匹配优先 20 int PlaceabelFirst( SimpleEdgeInfo* edge ); // 可装入优先 21 22 void findEqualToEdgeWidth( SimpleEdgeInfo * edge ); // 查找和ek的width大小相等的image 从ii开始在imageInfos里查找 23 void findLessOrEqualToEdgeWidth( SimpleEdgeInfo * edge ); // 查找不小于ek的width的image 从ii开始在imageInfos里查找 24 void findJointEqualToEdgeWidth( SimpleEdgeInfo * edge ); 25 bool canLeftFill( SimpleImageInfo * image, SimpleEdgeInfo * edge ); // 是否能左填平 26 bool canRightFill( SimpleImageInfo * image, SimpleEdgeInfo * edge ); // 是否能右填平 27 int getBigImageHeight(); 28 private: 29 int H_packing; 30 std::vector<SimpleImageInfo*> imageInfos; 31 std::vector<SimpleEdgeInfo*> edgeInfos; 32 std::vector<SimpleImageInfo*> equalWidthImages; 33 std::vector<SimpleImageInfo*> lessOrEqualWidthImages; 34 std::vector<SimpleImageInfo*> joint_EqualWidthImages; 35 int m_bigImageWidth; 36 37 }; 38 #endif
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "LLABF_Algorithm.h" 2 #include "cocos2d.h" 3 4 LLABF::LLABF() 5 :m_bigImageWidth(0) 6 { 7 8 } 9 10 LLABF::~LLABF() 11 { 12 for ( int i = 0; i< edgeInfos.size(); i++ ) 13 { 14 delete edgeInfos[i]; 15 } 16 edgeInfos.clear(); 17 } 18 19 20 void LLABF::initImages( std::vector<SimpleImageInfo*> & images, int width ) 21 { 22 imageInfos.clear(); 23 edgeInfos.clear(); 24 equalWidthImages.clear(); 25 lessOrEqualWidthImages.clear(); 26 joint_EqualWidthImages.clear(); 27 imageInfos = images; 28 m_bigImageWidth = width; 29 } 30 31 32 void LLABF::runLLABF() 33 { 34 // 初始化E 35 36 SimpleEdgeInfo * first_edge = new SimpleEdgeInfo(); 37 first_edge->x = 0; 38 first_edge->y = 0; 39 first_edge->width = 512; 40 first_edge->id = 0; 41 edgeInfos.push_back(first_edge); 42 43 44 SimpleEdgeInfo * the_lowest_line_edge = NULL; 45 int the_lowest_y = 0; 46 47 for ( int i = 0; i < imageInfos.size(); i++ ) 48 { 49 // 在E中选取最低水平线ek 50 the_lowest_line_edge = edgeInfos[0]; 51 the_lowest_y = the_lowest_line_edge->y; 52 for ( int j = 0 ; j< edgeInfos.size(); j++ ) 53 { 54 if ( the_lowest_y > edgeInfos[j]->y ) 55 { 56 the_lowest_y = edgeInfos[j]->y; 57 the_lowest_line_edge = edgeInfos[j]; 58 } 59 } // 选取结束 60 // 完全匹配优先 61 int m = -1; 62 // 传入参数为第几个矩形 i 为从哪个小image开始在imageInfos内搜索 , , 第几条ek边 63 if ( the_lowest_line_edge ) 64 { 65 m = FullFitFirst( the_lowest_line_edge ); // 选取的第m个小image 66 // 宽度匹配优先 67 if ( m <= -1 ) 68 { 69 m = WidthFitFirst( the_lowest_line_edge ); 70 } 71 // 高度匹配优先 72 if ( m<= -1) 73 { 74 m = HeightFitFirst( the_lowest_line_edge ); 75 } 76 // 组合宽度匹配优先 77 if ( m<= -1 ) 78 { 79 m = JointWidthFitFirst( the_lowest_line_edge ); 80 } 81 // 可装入优先 82 if ( m<= -1 ) 83 { 84 m = PlaceabelFirst( the_lowest_line_edge ); 85 } 86 87 if ( m <=-1 ) 88 { 89 i--; 90 // 合并E中的边 blablabla 怎么合并啊 91 if ( edgeInfos.size() > 1 ) 92 { 93 if ( the_lowest_line_edge->x == 0 && the_lowest_line_edge->x + the_lowest_line_edge->width < m_bigImageWidth ) 94 { 95 the_lowest_line_edge->y = edgeInfos[the_lowest_line_edge->id + 1]->y; 96 the_lowest_line_edge->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id + 1 ]->width; 97 edgeInfos[the_lowest_line_edge->id + 1 ]->isDelete = true; 98 } 99 else if ( the_lowest_line_edge->x + the_lowest_line_edge->width >= m_bigImageWidth) 100 { 101 edgeInfos[the_lowest_line_edge->id - 1]->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id - 1 ]->width; 102 the_lowest_line_edge->isDelete = true; 103 } 104 else 105 { 106 // 从两边选择值较小的进行合并 107 if ( edgeInfos[the_lowest_line_edge->id - 1]->y < edgeInfos[the_lowest_line_edge->id + 1]->y ) 108 { 109 edgeInfos[the_lowest_line_edge->id - 1]->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id - 1 ]->width; 110 the_lowest_line_edge->isDelete = true; 111 } 112 else 113 { 114 the_lowest_line_edge->y = edgeInfos[the_lowest_line_edge->id + 1]->y; 115 the_lowest_line_edge->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id + 1 ]->width; 116 edgeInfos[the_lowest_line_edge->id + 1 ]->isDelete = true; 117 } 118 } 119 std::vector<SimpleEdgeInfo*>::iterator it; 120 121 for( it= edgeInfos.begin();it!=edgeInfos.end();){ 122 123 if((*it)->isDelete){ 124 delete (*it); 125 edgeInfos.erase(it); 126 break; 127 } 128 ++it; 129 } 130 131 for ( int i = 0; i< edgeInfos.size(); i++ ) 132 { 133 edgeInfos[i]->id = i; 134 } 135 } 136 137 } 138 else 139 { 140 //将I集合中第j位元素对应的矩形件I(j)装入在E中的最低水平线ek上; blablabla 141 142 SimpleImageInfo * I_m = imageInfos[m]; 143 I_m->x = the_lowest_line_edge->x; 144 I_m->y = the_lowest_line_edge->y; 145 I_m->isInRect = true; 146 cocos2d::log("image name : %s", I_m->imageName.c_str()); 147 148 int i_m_width = I_m->width; 149 int i_m_height = I_m->height; 150 if ( I_m->isRotate ) 151 { 152 i_m_width = I_m->height; 153 i_m_height = I_m->width; 154 } 155 156 if ( the_lowest_line_edge->width - i_m_width > 0 ) // 如果宽度不同时,则实现的是高度匹配或者是组合宽度匹配 或者 可装入优先匹配 此时x轴不变 变化的是edge的宽度和 高度,还会产生新的边 157 { 158 SimpleEdgeInfo * newEdgeInfo = new SimpleEdgeInfo(); 159 newEdgeInfo->id = the_lowest_line_edge->id + 1; 160 newEdgeInfo->x = the_lowest_line_edge->x + i_m_width; // 新边的x值 161 newEdgeInfo->y = the_lowest_line_edge->y; // 新边的高度 和以前的相等 162 newEdgeInfo->width = the_lowest_line_edge->width - i_m_width; // 新边的宽度 163 164 edgeInfos.insert(edgeInfos.begin() + newEdgeInfo->id, newEdgeInfo); 165 166 the_lowest_line_edge->y = the_lowest_line_edge->y + i_m_height; 167 the_lowest_line_edge->width = i_m_width; 168 169 } 170 else // 宽度相同时 不产生新的edge 则直接将ek进行更新 此时x坐标不变 宽度不变 171 { 172 the_lowest_line_edge->y = the_lowest_line_edge->y + i_m_height; 173 } 174 if ( I_m->isRotate ) 175 { 176 if ( H_packing < I_m->x + I_m->width ) 177 { 178 H_packing = I_m->x + I_m->width; 179 } 180 } 181 else 182 { 183 if ( H_packing < I_m->x + I_m->height ) 184 { 185 H_packing = I_m->x + I_m->height; 186 } 187 } 188 // 更新I 和E 集合 189 // 主要是更新集合E 更新Ei 的id 以及x y width 合并Ei 和Ei+1 190 for ( int i = 0; i< edgeInfos.size(); i++ ) 191 { 192 edgeInfos[i]->id = i; 193 } 194 bool _tmp_delete = false; 195 for(int i = edgeInfos.size() - 1; i > 0 ;i-- ) 196 { 197 if ( edgeInfos[i]->y == edgeInfos[i-1]->y ) 198 { 199 edgeInfos[i-1]->width = edgeInfos[i-1]->width + edgeInfos[i]->width; 200 edgeInfos[i]->isDelete = true; 201 _tmp_delete = true; 202 } 203 } 204 205 if ( _tmp_delete ) 206 { 207 std::vector<SimpleEdgeInfo*>::iterator it; 208 209 for( it= edgeInfos.begin();it!=edgeInfos.end();){ 210 211 if((*it)->isDelete){ 212 delete (*it); 213 edgeInfos.erase(it); 214 break; 215 } 216 ++it; 217 } 218 219 for ( int i = 0; i< edgeInfos.size(); i++ ) 220 { 221 edgeInfos[i]->id = i; 222 } 223 } 224 for ( int i = 0; i< edgeInfos.size(); i++ ) 225 { 226 cocos2d::log( " id :%d , x : %d, y : %d, width : %d ",edgeInfos[i]->id, edgeInfos[i]->x,edgeInfos[i]->y, edgeInfos[i]->width) ; 227 } 228 } 229 } 230 } 231 } 232 233 // 完全匹配优先 234 /* 在可装入的轮廓线中选取最低的水平线ek,如果有多个线段,则优先选取最左边的一段. 235 从待装矩形中按照装入序列依次将矩形与ek进行比较, 236 如果存在宽度或者高度与该线段宽度ek.w相等 237 且装入后刚好左填平或者右填平的矩形则优先装入. 238 完全匹配优先能够减少装入后产生的轮廓线数量,使得装入轮廓朝着顶部平齐的方向发展. 239 */ 240 int LLABF::FullFitFirst( SimpleEdgeInfo* edge ) // 241 { 242 int result = -1; 243 // 存储宽度或者高度与edge相匹配的image 244 findEqualToEdgeWidth( edge); 245 246 if ( equalWidthImages.size() > 0 ) // 存在和edge的宽度相等的image 然后求左填平 右填平 247 { 248 for( int i = 0; i < equalWidthImages.size(); i++ ) 249 { 250 if ( canLeftFill(equalWidthImages[i],edge) || canRightFill(equalWidthImages[i],edge)) 251 { 252 result = equalWidthImages[i]->id; 253 break; 254 } 255 256 } 257 } 258 return result; 259 } 260 261 /************************************************************************/ 262 /* 263 在装入过程中,优先装入宽度或者高度与最低水平线ek等宽的矩形, 264 如果存在多个匹配矩形,则优先装入面积最大的.与完全匹配优先规则不同的是, 265 宽度匹配优先并不要求装入后能够实现左填平或者右填平; 266 同时,该规则使得较小矩形有推迟装入的趋势.另外,WFF不会增加装入轮廓线数量. 267 */ 268 /************************************************************************/ 269 int LLABF::WidthFitFirst( SimpleEdgeInfo* edge ) 270 { 271 int result = -1; 272 // 存储宽度或者高度与edge相匹配的image 273 findEqualToEdgeWidth(edge); 274 if ( equalWidthImages.size() > 0 ) // 存在和edge的宽度相等的image 然后求矩形面积最大的image 275 { 276 int max_area = 0; 277 int tmp_area; 278 SimpleImageInfo* max_area_image; 279 for( int i = 0; i < equalWidthImages.size(); i++ ) 280 { 281 tmp_area = equalWidthImages[i]->width * equalWidthImages[i]->height; 282 if ( max_area < tmp_area ) 283 { 284 max_area = tmp_area; 285 max_area_image = equalWidthImages[i]; 286 } 287 288 } 289 result = max_area_image->id; 290 } 291 return result; 292 } 293 /************************************************************************/ 294 /* 295 在待装矩形中,按照装入序列查询宽度或高度不大于最低水平线ek宽度且装入后能够实现左填平的矩形, 296 若存在则装入查询到的首个矩形. 297 与FFF和WFF不同,HFF可能会在最低水平线上产生新的、更小的可装入区域, 298 但却增加了轮廓线ek−1的宽度. 299 */ 300 /************************************************************************/ 301 int LLABF::HeightFitFirst( SimpleEdgeInfo* edge ) 302 { 303 int result = -1; 304 // 存储宽度或者高度不大于 edge.width 的 image 305 findLessOrEqualToEdgeWidth( edge ); 306 if ( lessOrEqualWidthImages.size() > 0 ) // 存在 则判断是否能左填平 307 { 308 for ( int i = 0; i< lessOrEqualWidthImages.size(); i++ ) 309 { 310 if ( canLeftFill( lessOrEqualWidthImages[i] , edge ) ) 311 { 312 result = lessOrEqualWidthImages[i]->id; 313 } 314 } 315 } 316 return result; 317 } 318 /************************************************************************/ 319 /* 320 按装入序列对两个矩形进行组合,如果组合后的宽度与最低水平线宽度ek相等,则优先装入组合序列中的首个矩形. 321 例如,存在两种组合I(i1).w+I(j1).w=ek.w, I(i2).w+I(j2).w=ek.w, 322 如果I(i1)的面积大于I(i2),则首先装入I(i1),否则装入I(i2). 323 */ 324 /************************************************************************/ 325 int LLABF::JointWidthFitFirst( SimpleEdgeInfo* edge ) 326 { 327 int result = -1; 328 329 findJointEqualToEdgeWidth(edge); 330 331 if ( joint_EqualWidthImages.size() > 0 ) // 存在组合和edge的宽度相等的image 然后求矩形面积最大的image 332 { 333 int max_area = 0; 334 int tmp_area; 335 SimpleImageInfo* max_area_image; 336 for( int i = 0; i < joint_EqualWidthImages.size(); i++ ) 337 { 338 tmp_area = joint_EqualWidthImages[i]->width * joint_EqualWidthImages[i]->height; 339 if ( max_area < tmp_area ) 340 { 341 max_area = tmp_area; 342 max_area_image = joint_EqualWidthImages[i]; 343 } 344 345 } 346 result = max_area_image->id; 347 } 348 return result; 349 } 350 /************************************************************************/ 351 /* 352 在一定范围内,从待装矩形件中按照装入序列依次查找宽度或高度不大于最低水平线ek宽度的矩形, 353 若存在,则将其装入;若存在多个,则装入面积最大的矩形. 354 PF可能在最低水平线上产生新的、更小的可装入区域,同时使得较小矩形延迟装入. 355 */ 356 /************************************************************************/ 357 int LLABF::PlaceabelFirst( SimpleEdgeInfo* edge ) 358 { 359 int result = -1; 360 361 findLessOrEqualToEdgeWidth(edge); 362 363 if ( lessOrEqualWidthImages.size() > 0 ) // 存在和edge的宽度相等的image 然后求矩形面积最大的image 364 { 365 int max_area = 0; 366 int tmp_area; 367 SimpleImageInfo* max_area_image; 368 for( int i = 0; i < lessOrEqualWidthImages.size(); i++ ) 369 { 370 tmp_area = lessOrEqualWidthImages[i]->width * lessOrEqualWidthImages[i]->height; 371 if ( max_area < tmp_area ) 372 { 373 max_area = tmp_area; 374 max_area_image = lessOrEqualWidthImages[i]; 375 } 376 } 377 result = max_area_image->id; 378 } 379 380 return result; 381 } 382 383 void LLABF::findEqualToEdgeWidth( SimpleEdgeInfo * edge ) 384 { 385 equalWidthImages.clear(); 386 for ( int i = 0; i < imageInfos.size(); i++ ) 387 { 388 if ( !imageInfos[i]->isInRect ) 389 { 390 if ( imageInfos[i]->width == edge->width ) 391 { 392 equalWidthImages.push_back(imageInfos[i]); 393 imageInfos[i]->isRotate = false; 394 } 395 else if ( imageInfos[i]->height == edge->width ) 396 { 397 equalWidthImages.push_back(imageInfos[i]); 398 imageInfos[i]->isRotate = true; 399 } 400 } 401 } 402 } 403 404 405 void LLABF::findLessOrEqualToEdgeWidth( SimpleEdgeInfo * edge ) 406 { 407 lessOrEqualWidthImages.clear(); 408 for ( int i = 0; i < imageInfos.size(); i++ ) 409 { 410 if ( !imageInfos[i]->isInRect ) 411 { 412 if ( imageInfos[i]->width <= edge->width ) 413 { 414 lessOrEqualWidthImages.push_back(imageInfos[i]); 415 imageInfos[i]->isRotate = false; 416 } 417 else if ( imageInfos[i]->height <= edge->width ) 418 { 419 lessOrEqualWidthImages.push_back(imageInfos[i]); 420 imageInfos[i]->isRotate = true; 421 } 422 } 423 } 424 } 425 426 // 按装入序列对两个矩形进行组合,如果组合后的宽度与最低水平线宽度ek相等,则优先装入组合序列中的首个矩形 427 void LLABF::findJointEqualToEdgeWidth( SimpleEdgeInfo * edge ) 428 { 429 joint_EqualWidthImages.clear(); 430 int i_image_width = 0; 431 int j_image_width = 0; 432 int edge_width = edge->width; 433 for ( int i = 0; i< imageInfos.size(); i++ ) 434 { 435 if ( !imageInfos[i]->isInRect ) 436 { 437 i_image_width = imageInfos[i]->width; 438 j_image_width = edge_width - i_image_width; 439 for ( int j = 0; j< imageInfos.size(); j++ ) 440 { 441 if ( !imageInfos[j]->isInRect ) 442 { 443 if ( j_image_width == imageInfos[j]->width ) 444 { 445 joint_EqualWidthImages.push_back(imageInfos[i]); 446 } 447 448 } 449 450 } 451 } 452 } 453 } 454 455 456 457 bool LLABF::canLeftFill( SimpleImageInfo * image, SimpleEdgeInfo * edge ) 458 { 459 if ( edge->id == 0 ) 460 { 461 return false; 462 } 463 int tmpHeight = image->height; 464 if ( image->isRotate ) 465 { 466 tmpHeight = image->width; 467 } 468 469 if ( tmpHeight == (edgeInfos[ edge->id - 1 ]->y - edge->y)) // 如果 image的高度和ek-1 - ek 的高度差相等,即可实现左填平 470 { 471 return true; 472 } 473 return false; 474 } 475 476 bool LLABF::canRightFill( SimpleImageInfo * image, SimpleEdgeInfo * edge ) 477 { 478 if ( edge->id == edgeInfos.size() - 1 ) 479 { 480 return false; 481 } 482 int tmpHeight = image->height; 483 if ( image->isRotate ) 484 { 485 tmpHeight = image->width; 486 } 487 if ( image->height == (edgeInfos[ edge->id + 1 ]->y - edge->y)) // 如果 image的高度和ek+1 - ek 的高度差相等,即可实现右填平 488 { 489 return true; 490 } 491 return false; 492 } 493 494 int LLABF::getBigImageHeight() 495 { 496 // 在E中选取最低水平线ek 497 int the_highest_y = 0; 498 for ( int j = 0 ; j< edgeInfos.size(); j++ ) 499 { 500 if ( the_highest_y < edgeInfos[j]->y ) 501 { 502 the_highest_y = edgeInfos[j]->y; 503 } 504 } // 选取结束 505 return the_highest_y; 506 }
里面都有注释,参照着论文应该都能明白。希望以后自己能够看懂。
下面就是根据信息,将小图的数据copy至大图内。小图旋转90度复制像素时,着实被坑了一把,后来方法没怎么改,只是将小图坐标固定,然后根据小图坐标去求大图坐标,也不知道怎么就正确了,玛蛋。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #ifndef __BIG_IMAGE_H__ 2 #define __BIG_IMAGE_H__ 3 #include "cocos2d.h" 4 #include "HelloWorldScene.h" 5 class SimpleImageInfo; 6 class BigImage 7 { 8 public : 9 BigImage(); 10 ~BigImage(); 11 BigImage( std::string bigFileName, int width, int height , std::string filePlist ); 12 13 void init(); 14 void publish(); 15 void initBigImageWithImagesData( std::vector< SimpleImageInfo* > * images ); 16 private: 17 std::vector<SimpleImageInfo*> * imageInfos; 18 int m_dstImageWidth; 19 int m_dstImageHeight; 20 int m_dataLen; 21 int m_dst_ptr; 22 int m_dst_pixels_start_ptr; 23 unsigned char * m_dstImageData; 24 std::string m_bigImageName; 25 std::string m_plistName; 26 }; 27 28 29 #endif
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include "BigImage.h" 2 //#include "cocos2d/external/png/include/win32/png.h" 3 //#include "cocos2d/cocos/2d/platform/CCImage.h" 4 #include <math.h> 5 #include <iostream> 6 USING_NS_CC; 7 8 BigImage::BigImage() 9 :m_dstImageWidth(0) 10 ,m_dstImageHeight(0) 11 ,m_dst_ptr(0) 12 ,m_dst_pixels_start_ptr(0) 13 ,m_dataLen(0) 14 ,m_dstImageData(NULL) 15 ,m_bigImageName("test.png") 16 { 17 18 } 19 20 BigImage::~BigImage() 21 { 22 free(m_dstImageData); 23 } 24 BigImage::BigImage( std::string bigFileName, int width, int height , std::string filePlist ) 25 { 26 m_bigImageName = bigFileName; 27 m_dstImageWidth = width; 28 m_dstImageHeight = height; 29 m_plistName = filePlist; 30 m_dataLen = m_dstImageWidth * m_dstImageHeight * 4; 31 this->init(); 32 } 33 34 void BigImage::init() 35 { 36 m_dstImageData = (unsigned char* )malloc(m_dstImageWidth * m_dstImageHeight * 4 ); 37 memset(m_dstImageData,0,m_dataLen); 38 } 39 40 void BigImage::publish() 41 { 42 std::string writablePath = FileUtils::getInstance()->getWritablePath(); 43 std::string fullPath_imageName = writablePath + m_bigImageName; 44 45 Image * new_image = new Image(); 46 new_image->initWithRawData( m_dstImageData,m_dataLen ,m_dstImageWidth,m_dstImageHeight,8 ); 47 new_image->saveToFile( fullPath_imageName,false); 48 49 // plist的根节点 50 Dictionary * dict_root = Dictionary::create(); 51 // plist下的frames节点 其内部为各个子图的信息节点 52 auto dict_frames = Dictionary::create(); 53 for ( int i = 0; i< imageInfos->size(); i++ ) 54 { 55 // small image name 56 SimpleImageInfo* srcImage = imageInfos->at(i); 57 auto imageDict = Dictionary::create(); 58 // frame 位置 图片大小 59 auto str_frame = String::createWithFormat("{{%d,%d},{%d,%d}}", srcImage->x,srcImage->y,srcImage->width,srcImage->height ); 60 imageDict->setObject(str_frame,"frame"); 61 // offset 62 auto str_offset = String::createWithFormat("{%d,%d}",0,0 ); 63 imageDict->setObject(str_offset,"offset"); 64 // is rotated ? 65 auto b_rotate = Bool::create(srcImage->isRotate); 66 imageDict->setObject( b_rotate,"rotated"); 67 // sourceColorRect 68 auto str_colorRect = String::createWithFormat("{{%d,%d},{%d,%d}}", 0,0,srcImage->width,srcImage->height ); 69 imageDict->setObject(str_colorRect,"sourceColorRect"); 70 // sourceSize 71 auto str_sourceSize = String::createWithFormat("{%d,%d}", srcImage->width,srcImage->height ); 72 imageDict->setObject(str_sourceSize,"sourceSize"); 73 74 dict_frames->setObject( imageDict,srcImage->imageName ); 75 } 76 77 dict_root->setObject( dict_frames, "frames"); 78 79 // 根节点下的元数据节点 80 auto dict_metadata = Dictionary::create(); 81 82 auto intObject = Integer::create(2); 83 dict_metadata->setObject(intObject,"format"); 84 85 auto strObjectRealName = String::create(m_bigImageName); 86 dict_metadata->setObject(strObjectRealName,"realTextureFileName"); 87 88 auto strObjectSize = String::createWithFormat("{%d,%d}", m_dstImageWidth, m_dstImageHeight); 89 dict_metadata->setObject( strObjectSize, "size"); 90 91 auto strObjectTextureFileName = String::create(m_bigImageName); 92 dict_metadata->setObject(strObjectTextureFileName,"textureFileName"); 93 94 dict_root->setObject( dict_metadata,"metadata"); 95 96 std::string fullPath_plist = writablePath + m_plistName; 97 if(dict_root->writeToFile(fullPath_plist.c_str())) 98 log("see the plist file at %s", fullPath_plist.c_str()); 99 else 100 log("write plist file failed"); 101 } 102 103 void BigImage::initBigImageWithImagesData( std::vector<SimpleImageInfo*> * images ) 104 { 105 imageInfos = images; 106 107 for ( int i = 0; i< images->size(); i++ ) 108 { 109 SimpleImageInfo* srcImageInfo = images->at(i); 110 Image * srcImage = new Image(); 111 srcImage->initWithImageFile( srcImageInfo->imageName ); 112 unsigned char * srcImageData = srcImage->getData(); 113 ssize_t dataLen = srcImage->getDataLen(); 114 int srcImageWidth = srcImage->getWidth(); 115 int srcImageHeight = srcImage->getHeight(); 116 117 int nbitPerPixels = srcImage->getBitPerPixel(); 118 119 Point position = Point(srcImageInfo->x,srcImageInfo->y ); 120 int src_pixels_ptr = 0; 121 122 if ( srcImageInfo->isRotate ) 123 { 124 int target_ptr = ( position.y * m_dstImageWidth + position.x ) * 4; 125 126 src_pixels_ptr = ( srcImageWidth * srcImageHeight - srcImageWidth ) * 4; 127 128 int target_pixels_start_ptr = target_ptr; 129 int src_pixels_start_ptr = src_pixels_ptr; 130 131 int inner_line_dert = m_dstImageWidth * 4; // 每一行的数据 132 int src_line_dert = 4 ; // 每一行的数据个数 133 134 for ( int i = 0; i< srcImageWidth; i++ ) 135 { 136 for ( int j = 0; j < srcImageHeight; j++ ) 137 { 138 m_dstImageData[ target_ptr ] = srcImageData[ src_pixels_ptr ]; 139 m_dstImageData[ target_ptr + 1 ] = srcImageData[ src_pixels_ptr + 1 ]; 140 m_dstImageData[ target_ptr + 2 ] = srcImageData[ src_pixels_ptr + 2 ]; 141 m_dstImageData[ target_ptr + 3 ] = srcImageData[ src_pixels_ptr + 3 ]; 142 target_ptr += 4; 143 src_pixels_ptr -= srcImageWidth * 4 ; 144 } 145 146 target_pixels_start_ptr += inner_line_dert; 147 src_pixels_start_ptr += src_line_dert; 148 149 src_pixels_ptr = src_pixels_start_ptr; 150 target_ptr = target_pixels_start_ptr; 151 } 152 } 153 else 154 { 155 int target_ptr = ( position.y * m_dstImageWidth + position.x ) * 4; 156 157 int src_pixels_start_ptr = src_pixels_ptr; 158 int target_pixels_start_ptr = target_ptr; 159 160 int inner_line_dert = m_dstImageWidth * 4; // 每一行的数据 161 int src_line_dert = srcImageWidth * 4; // 每一行的数据个数 162 163 for ( int i = 0; i< srcImageHeight; i++ ) 164 { 165 for ( int j = 0; j < srcImageWidth; j++ ) 166 { 167 m_dstImageData[ target_ptr ] = srcImageData[ src_pixels_ptr ]; 168 m_dstImageData[ target_ptr + 1 ] = srcImageData[ src_pixels_ptr + 1 ]; 169 m_dstImageData[ target_ptr + 2 ] = srcImageData[ src_pixels_ptr + 2 ]; 170 m_dstImageData[ target_ptr + 3 ] = srcImageData[ src_pixels_ptr + 3 ]; 171 target_ptr += 4; 172 src_pixels_ptr += 4; 173 } 174 175 target_pixels_start_ptr += inner_line_dert; 176 src_pixels_start_ptr += src_line_dert; 177 178 src_pixels_ptr = src_pixels_start_ptr; 179 target_ptr = target_pixels_start_ptr; 180 } 181 182 } 183 184 185 delete srcImage; 186 } 187 188 }
估计可能是刚开始计算时,是按照左下角是坐标原点计算的。但是,实际上Image的data是以左上角为(0,0)点的。所以尽管方法对,但是因为一个是左手坐标系,一个是右手坐标系,所以才导致错误的。
这样看,首先确定坐标原点是多么重要的一件事。
LLABF算法,还没有texturepacker采用的maxRects算法优。但是结果还算是可以吧,加上人的智慧,其实也可以合出不错的图。
大图的高度是根据LLABF算法返回的数据确定的,没有取2的整数次幂。想取的可以取。感觉不太必要。
本博文的原意是解释一下程序的,但是发现注释其实挺多的,还有论文做参照,就不卖弄了。
遗传算法是个什么玩意?有时间再搞吧,加上遗传算法不知道是不是就会超过maxRects算法了,值得期待,期待无期啊。。。。。%>_<%。。。。
差点忘了,hb说,可以类似于kd树的方法去做,首先选出面积最大的小图,放入后,整个空间就被该图将剩余空间划分成了左右子树,然后在各个子树内采用同样的方式放入剩余小图。
没有细想,有看到这篇博文的可以试下,hb说这种树的方式算是最优的了。我没有试,所以不造真假。