2-D矩形装箱问题

研究2-D矩形装箱问题,是因为需要将小图拼成大图,作为一个大的texture加载到内存内,从而实现减少内存消耗的目的。,按照论文内提到的一种算法,写了写程序。论文是: 二维矩形条带装箱问题的底部左齐择优匹配算法。

程序运行环境为:cocos2dx 3.0, vs2012.

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
#include "BigImage.h"
#include "BaseObject.h"
#include "LLABF_Algorithm.h"

class BigImage;
class HelloWorld : public cocos2d::Layer
{
public:
    ~HelloWorld();
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();  
    
    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    void initSimpleImageInfo( SimpleImageInfo * simpleImage );

    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);

    std::vector<SimpleImageInfo*> imageInfos;

    // 图像像素拷贝   将一张图的内容复制到另一张大图中   
    void copyPixels();

    BigImage * m_bigImage;
    void initTestImageInfos();
    void initTestDictionary();
};

#endif // __HELLOWORLD_SCENE_H__

#include "HelloWorldScene.h"
#include "cocos2d/external/png/include/win32/png.h"
#include  <math.h>
#include  <iostream>
#include  <fstream>
#include  <string>

using namespace std;

USING_NS_CC;

Scene* HelloWorld::createScene()
{
    auto scene = Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();
    Point origin = Director::getInstance()->getVisibleOrigin();
    auto closeItem = MenuItemImage::create(
                                           "CloseNormal.png",
                                           "CloseSelected.png",
                                           CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
    
    closeItem->setPosition(Point(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
                                origin.y + closeItem->getContentSize().height/2));
    auto menu = Menu::create(closeItem, NULL);
    menu->setPosition(Point::ZERO);
    this->addChild(menu, 1);
    auto label = LabelTTF::create("Hello World", "Arial", 24);

    label->setPosition(Point(origin.x + visibleSize.width/2,
                            origin.y + visibleSize.height - label->getContentSize().height));

    return true;
}


void HelloWorld::menuCloseCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
    MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
    return;
#endif
    imageInfos.clear();
    system("dir /b *.png > untitled.plist");
    int bigImageWidth = 512;
    string fullPath = FileUtils::getInstance()->fullPathForFilename("untitled.plist");
    string filecontent = FileUtils::getInstance()->getStringFromFile(fullPath);
    ifstream fp( fullPath );
    if (!fp)
    {
        cerr << "OPEN ERROR" << endl;
        return ;
    }

    if ( fp )
    {
        string s;
        while (getline(fp,s))
        {
            SimpleImageInfo * image = new SimpleImageInfo();
            image->imageName = s;
            imageInfos.push_back(image);
            CCLog( s.c_str()) ;
        }
        fp.close();

        // 初始化I   
        for ( int i = 0; i< imageInfos.size(); i++ )
        {
            initSimpleImageInfo(imageInfos[i]);
            imageInfos[i]->id = i;
        }
        LLABF * llabf = new LLABF();
        llabf->initImages(imageInfos, bigImageWidth );
        llabf->runLLABF();
        


        m_bigImage = new BigImage("my_test_1.png",bigImageWidth,llabf->getBigImageHeight(),"my_test_1.plist");
        m_bigImage->initBigImageWithImagesData(&imageInfos);
        m_bigImage->publish();
    }

    CCLog("end");

    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("my_test_1.plist");
    //auto sprite = Sprite::createWithSpriteFrameName("a1.png");
    //sprite->setPosition( 300, 300);
    //this->addChild(sprite, 1000);

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}

void HelloWorld::initSimpleImageInfo( SimpleImageInfo * simpleImage )
{
    // 读取png文件头    
        std::string fullPath_png = FileUtils::getInstance()->fullPathForFilename( simpleImage->imageName );
        unsigned char header[8];
        int width, height;
        png_byte color_type; //图片到类型(可能会用在是否是开启来通道)  
        png_byte bit_depth; //字节深度  

        png_structp png_ptr;  // 图片  
        png_infop info_ptr;   // 图片的信息  
        int number_of_passes;// 隔行扫描  
        png_bytep * row_pointers;// 图片的数据内容  
        int  row, col, pos; // 用于改变png像素排列的问题  
        GLubyte * rgba;

        FILE * fp = fopen( fullPath_png.c_str(), "rb");
        if ( !fp )
        {
            fclose(fp);
            return;
        }
        fread(header, 1, 8, fp);
        if ( png_sig_cmp(header, 0, 8 ))  // 读取文件头判断是否是png图片,不是则做出相应处理   
        {
            fclose(fp);
            return;
        }
        //根据libpng的libpng-manual.txt的说明使用文档 接下来必须初始化png_structp 和 png_infop  
        png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL); //后三个是绑定错误以及警告的函数这里设置为空  

        if(!png_ptr)
        {
            fclose(fp);
            return;
        }
        //根据初始化的png_ptr初始化png_infop  
        info_ptr=png_create_info_struct(png_ptr);  
        if(!info_ptr)  
        {  
            //初始化失败以后销毁png_structp  
            png_destroy_read_struct(&png_ptr,(png_infopp)NULL,(png_infopp)NULL);  
            fclose(fp);  
            return ;  
        }  
        //老老实实按照libpng给到的说明稳定步骤来  错误处理!  
        if (setjmp(png_jmpbuf(png_ptr)))  
        {  
            //释放占用的内存!然后关闭文件返回一个贴图ID此处应该调用一个生成默认贴图返回ID的函数  

            png_destroy_read_struct(&png_ptr,(png_infopp)NULL,(png_infopp)NULL);  

            fclose(fp);  

            return ;  

        }  
        //你需要确保是通过2进制打开的文件。通过i/o定制函数png_init_io  
        png_init_io(png_ptr,fp);  
        //似乎是说要告诉libpng文件从第几个开始missing  
        png_set_sig_bytes(png_ptr, 8);  
        //如果你只想简单的操作你现在可以实际读取图片信息了!  
        png_read_info(png_ptr, info_ptr);  
        //获得图片到信息 width height 颜色类型  字节深度  
        width = png_get_image_width(png_ptr, info_ptr);  
        height = png_get_image_height(png_ptr, info_ptr);  
        color_type = png_get_color_type(png_ptr, info_ptr);  
        //如果图片带有alpha通道就需要  
        // if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)  

        // png_set_swap_alpha(png_ptr);  
        bit_depth = png_get_bit_depth(png_ptr, info_ptr);  

        fclose(fp);  

        simpleImage->width = width;
        simpleImage->height = height;
}

void HelloWorld::initTestImageInfos()
{
      SimpleImageInfo * image1 = new SimpleImageInfo();
      image1->imageName = "a5.png";
      image1->x = 0;
      image1->y = 0;
      image1->width = 576;
      image1->height = 154;
      image1->isRotate = true;
      imageInfos.push_back(image1);

      SimpleImageInfo * image2 = new SimpleImageInfo();
      image2->imageName = "a6.png";
      image2->x = 154;
      image2->y = 0;
      image2->width = 576;
      image2->height = 154;
      image2->isRotate = true;
      imageInfos.push_back(image2);
}

HelloWorld::~HelloWorld()
{
    for ( int i = 0; i< imageInfos.size(); i++ )
    {
        delete imageInfos[i];
    }
    imageInfos.clear();
}

void HelloWorld::initTestDictionary()
{
    Dictionary * dict = Dictionary::create();
    auto string = String::create("test_key");
    dict->setObject(string, "string element key"); // 添加一个键值对   
    auto doubleObject = Double::create(1024.123);
    dict->setObject(doubleObject, "double");

    Dictionary * dict_1 = Dictionary::create();
    dict_1->setObject(String::create("string in dictInDict value"), "string in dictInDict key");
    dict->setObject(dict_1, "dict_test_key");

    std::string writablePath = FileUtils::getInstance()->getWritablePath();
    std::string fullPath = writablePath + "text.plist";
    if(dict->writeToFile(fullPath.c_str()))
        log("see the plist file at %s", fullPath.c_str());
    else
        log("write plist file failed");
}

其中用到了system函数,将Resource目录内的png图像导入untitled.plist文件内。其实这个应该做个UI,弹出文件夹选择框,多选文件,将文件名导入。我太懒了,不想去看MFC或者QT。就这样凑合吧。HelloWorldScene主要就是导出文件夹内的png文件名。然后初始化一些小图信息,转给LLABF算法,最后BigImage类根据LLABF算法计算得到的小图位置信息,生成大图png,以及plist。就这么简单。

下面就是LLABF算法。应该差不多是按照论文内描述的那样写的。

#ifndef  __LLABF_ALGORITHM_H_ 
#define  __LLABF_ALGORITHM_H_
#include "BaseObject.h"
#include <iostream>
#include <vector>

class LLABF
{
public:
    LLABF();
    ~LLABF();

    void initImages( std::vector<SimpleImageInfo*> & images , int bigImageWidth );
    void runLLABF();

    int FullFitFirst( SimpleEdgeInfo* edge );   // 完全匹配优先  
    int WidthFitFirst( SimpleEdgeInfo* edge );   // 宽度匹配优先  
    int HeightFitFirst( SimpleEdgeInfo* edge );  // 高度匹配优先  
    int JointWidthFitFirst( SimpleEdgeInfo* edge ); // 组合匹配优先  
    int PlaceabelFirst(  SimpleEdgeInfo* edge );     // 可装入优先   

    void findEqualToEdgeWidth( SimpleEdgeInfo * edge );  // 查找和ek的width大小相等的image 从ii开始在imageInfos里查找      
    void findLessOrEqualToEdgeWidth(  SimpleEdgeInfo * edge );  // 查找不小于ek的width的image  从ii开始在imageInfos里查找    
    void findJointEqualToEdgeWidth( SimpleEdgeInfo * edge );
    bool canLeftFill( SimpleImageInfo * image, SimpleEdgeInfo * edge );  // 是否能左填平  
    bool canRightFill( SimpleImageInfo * image, SimpleEdgeInfo * edge ); // 是否能右填平  
    int  getBigImageHeight();
private:
    int H_packing;
    std::vector<SimpleImageInfo*> imageInfos;
    std::vector<SimpleEdgeInfo*>  edgeInfos;
    std::vector<SimpleImageInfo*> equalWidthImages;
    std::vector<SimpleImageInfo*> lessOrEqualWidthImages;
    std::vector<SimpleImageInfo*> joint_EqualWidthImages;
    int m_bigImageWidth;

};
#endif

#include "LLABF_Algorithm.h"
#include "cocos2d.h"

LLABF::LLABF()
    :m_bigImageWidth(0)
{

}

LLABF::~LLABF()
{
    for ( int i = 0; i< edgeInfos.size(); i++ )
    {
        delete edgeInfos[i];
    }
    edgeInfos.clear();
}


void LLABF::initImages( std::vector<SimpleImageInfo*> & images, int width )
{
    imageInfos.clear();
    edgeInfos.clear();
    equalWidthImages.clear();
    lessOrEqualWidthImages.clear();
    joint_EqualWidthImages.clear();
    imageInfos = images;
    m_bigImageWidth = width;
}


void LLABF::runLLABF()
{
    // 初始化E   

    SimpleEdgeInfo * first_edge = new SimpleEdgeInfo();
    first_edge->x = 0;
    first_edge->y = 0;
    first_edge->width = 512;
    first_edge->id = 0;
    edgeInfos.push_back(first_edge);


    SimpleEdgeInfo * the_lowest_line_edge = NULL;
    int   the_lowest_y = 0;

    for ( int i = 0; i < imageInfos.size(); i++ )
    {
        // 在E中选取最低水平线ek   
        the_lowest_line_edge = edgeInfos[0];
        the_lowest_y = the_lowest_line_edge->y;
        for ( int j = 0 ; j< edgeInfos.size(); j++ )
        {
            if ( the_lowest_y > edgeInfos[j]->y )
            {
                the_lowest_y = edgeInfos[j]->y;
                the_lowest_line_edge = edgeInfos[j];
            }
        } // 选取结束   
        // 完全匹配优先   
        int m = -1;
        // 传入参数为第几个矩形  i 为从哪个小image开始在imageInfos内搜索 , , 第几条ek边 
        if ( the_lowest_line_edge )
        {
            m = FullFitFirst( the_lowest_line_edge ); // 选取的第m个小image  
            // 宽度匹配优先   
            if ( m <= -1 )
            {
                m = WidthFitFirst( the_lowest_line_edge );
            }
            // 高度匹配优先    
            if ( m<= -1)
            {
                m = HeightFitFirst( the_lowest_line_edge );
            }
            // 组合宽度匹配优先   
            if ( m<= -1 )
            {
                m = JointWidthFitFirst( the_lowest_line_edge );
            }
            // 可装入优先   
            if ( m<= -1 )
            {
                m = PlaceabelFirst( the_lowest_line_edge );
            }

            if ( m <=-1 )
            {
                i--;
                // 合并E中的边   blablabla   怎么合并啊       
                if ( edgeInfos.size() > 1 )
                {
                    if ( the_lowest_line_edge->x == 0 && the_lowest_line_edge->x + the_lowest_line_edge->width < m_bigImageWidth )
                    {
                        the_lowest_line_edge->y = edgeInfos[the_lowest_line_edge->id + 1]->y;
                        the_lowest_line_edge->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id + 1 ]->width;
                        edgeInfos[the_lowest_line_edge->id + 1 ]->isDelete = true;
                    }
                    else if ( the_lowest_line_edge->x + the_lowest_line_edge->width >= m_bigImageWidth)
                    {
                        edgeInfos[the_lowest_line_edge->id - 1]->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id - 1 ]->width;
                        the_lowest_line_edge->isDelete = true;
                    }
                    else
                    {
                        // 从两边选择值较小的进行合并   
                        if ( edgeInfos[the_lowest_line_edge->id - 1]->y < edgeInfos[the_lowest_line_edge->id + 1]->y )
                        {
                            edgeInfos[the_lowest_line_edge->id - 1]->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id - 1 ]->width;
                            the_lowest_line_edge->isDelete = true;
                        }
                        else
                        {
                            the_lowest_line_edge->y = edgeInfos[the_lowest_line_edge->id + 1]->y;
                            the_lowest_line_edge->width = the_lowest_line_edge->width + edgeInfos[the_lowest_line_edge->id + 1 ]->width;
                            edgeInfos[the_lowest_line_edge->id + 1 ]->isDelete = true;
                        }
                    }
                    std::vector<SimpleEdgeInfo*>::iterator it;

                    for(  it= edgeInfos.begin();it!=edgeInfos.end();){

                        if((*it)->isDelete){
                            delete (*it);
                            edgeInfos.erase(it);
                            break;
                        }
                        ++it;
                    }

                    for ( int i = 0; i< edgeInfos.size(); i++ )
                    {
                        edgeInfos[i]->id = i;
                    }
                }

            }
            else
            {
                //将I集合中第j位元素对应的矩形件I(j)装入在E中的最低水平线ek上; blablabla   

                SimpleImageInfo * I_m = imageInfos[m];
                I_m->x = the_lowest_line_edge->x;
                I_m->y = the_lowest_line_edge->y;
                I_m->isInRect = true;
                cocos2d::log("image name : %s", I_m->imageName.c_str());

                int i_m_width = I_m->width;
                int i_m_height = I_m->height;
                if ( I_m->isRotate )
                {
                    i_m_width = I_m->height;
                    i_m_height = I_m->width;
                }

                if ( the_lowest_line_edge->width - i_m_width > 0 )  // 如果宽度不同时,则实现的是高度匹配或者是组合宽度匹配 或者 可装入优先匹配  此时x轴不变 变化的是edge的宽度和 高度,还会产生新的边   
                {
                    SimpleEdgeInfo * newEdgeInfo = new SimpleEdgeInfo();
                    newEdgeInfo->id = the_lowest_line_edge->id + 1;
                    newEdgeInfo->x = the_lowest_line_edge->x + i_m_width; // 新边的x值  
                    newEdgeInfo->y = the_lowest_line_edge->y;  // 新边的高度 和以前的相等   
                    newEdgeInfo->width = the_lowest_line_edge->width - i_m_width;       // 新边的宽度   

                    edgeInfos.insert(edgeInfos.begin() + newEdgeInfo->id, newEdgeInfo);

                    the_lowest_line_edge->y = the_lowest_line_edge->y + i_m_height;
                    the_lowest_line_edge->width = i_m_width;

                }
                else   // 宽度相同时  不产生新的edge    则直接将ek进行更新  此时x坐标不变 宽度不变    
                {
                    the_lowest_line_edge->y = the_lowest_line_edge->y + i_m_height;
                }
                if ( I_m->isRotate )
                {
                    if ( H_packing < I_m->x + I_m->width )
                    {
                        H_packing = I_m->x + I_m->width;
                    }
                }
                else
                {
                    if ( H_packing < I_m->x + I_m->height )
                    {
                        H_packing = I_m->x + I_m->height;
                    }
                }
                // 更新I 和E 集合    
                // 主要是更新集合E   更新Ei 的id 以及x y width 合并Ei 和Ei+1  
                for ( int i = 0; i< edgeInfos.size(); i++ )
                {
                    edgeInfos[i]->id = i;
                }
                bool _tmp_delete = false;
                for(int i = edgeInfos.size() - 1; i > 0 ;i--  )
                {
                    if ( edgeInfos[i]->y == edgeInfos[i-1]->y )
                    {
                        edgeInfos[i-1]->width = edgeInfos[i-1]->width + edgeInfos[i]->width;
                        edgeInfos[i]->isDelete = true;
                        _tmp_delete = true;
                    }
                }

                if ( _tmp_delete )
                {
                    std::vector<SimpleEdgeInfo*>::iterator it;

                    for(  it= edgeInfos.begin();it!=edgeInfos.end();){

                        if((*it)->isDelete){
                            delete (*it);
                            edgeInfos.erase(it);
                            break;
                        }
                        ++it;
                    }

                    for ( int i = 0; i< edgeInfos.size(); i++ )
                    {
                        edgeInfos[i]->id = i;
                    }
                }
                for ( int i = 0; i< edgeInfos.size(); i++ )
                {
                    cocos2d::log( " id :%d , x : %d, y : %d, width : %d ",edgeInfos[i]->id, edgeInfos[i]->x,edgeInfos[i]->y, edgeInfos[i]->width) ;
                }
            }
        }
    }
}

//  完全匹配优先   
/*  在可装入的轮廓线中选取最低的水平线ek,如果有多个线段,则优先选取最左边的一段.   
    从待装矩形中按照装入序列依次将矩形与ek进行比较,   
    如果存在宽度或者高度与该线段宽度ek.w相等   
    且装入后刚好左填平或者右填平的矩形则优先装入.   
    完全匹配优先能够减少装入后产生的轮廓线数量,使得装入轮廓朝着顶部平齐的方向发展.  
*/
int LLABF::FullFitFirst( SimpleEdgeInfo* edge )  //
{
    int result = -1;
    // 存储宽度或者高度与edge相匹配的image  
    findEqualToEdgeWidth( edge);
    
    if ( equalWidthImages.size()  > 0 )  // 存在和edge的宽度相等的image   然后求左填平 右填平  
    {
        for( int i = 0; i < equalWidthImages.size(); i++ )
        {
            if ( canLeftFill(equalWidthImages[i],edge) || canRightFill(equalWidthImages[i],edge))
            {
                result = equalWidthImages[i]->id;
                break;
            }
            
        }
    }
    return result;
}

/************************************************************************/
/* 
在装入过程中,优先装入宽度或者高度与最低水平线ek等宽的矩形,  
如果存在多个匹配矩形,则优先装入面积最大的.与完全匹配优先规则不同的是,  
宽度匹配优先并不要求装入后能够实现左填平或者右填平; 
同时,该规则使得较小矩形有推迟装入的趋势.另外,WFF不会增加装入轮廓线数量. 
*/
/************************************************************************/
int LLABF::WidthFitFirst( SimpleEdgeInfo* edge )
{
    int result = -1;
    // 存储宽度或者高度与edge相匹配的image  
    findEqualToEdgeWidth(edge);
    if ( equalWidthImages.size()  > 0 )  // 存在和edge的宽度相等的image   然后求矩形面积最大的image  
    {
        int max_area = 0;
        int tmp_area;
        SimpleImageInfo* max_area_image;
        for( int i = 0; i < equalWidthImages.size(); i++ )
        {
            tmp_area = equalWidthImages[i]->width * equalWidthImages[i]->height;
            if ( max_area < tmp_area )
            {
                max_area = tmp_area;
                max_area_image = equalWidthImages[i];
            }
            
        }
        result = max_area_image->id;
    }
    return result;
}
/************************************************************************/
/* 
在待装矩形中,按照装入序列查询宽度或高度不大于最低水平线ek宽度且装入后能够实现左填平的矩形, 
若存在则装入查询到的首个矩形. 
与FFF和WFF不同,HFF可能会在最低水平线上产生新的、更小的可装入区域, 
但却增加了轮廓线ek−1的宽度. 
*/
/************************************************************************/
int LLABF::HeightFitFirst( SimpleEdgeInfo* edge )
{
    int result = -1;
    // 存储宽度或者高度不大于 edge.width 的 image   
    findLessOrEqualToEdgeWidth( edge );
    if ( lessOrEqualWidthImages.size() > 0 ) // 存在   则判断是否能左填平  
    {
        for ( int i = 0; i< lessOrEqualWidthImages.size(); i++ )
        {
            if ( canLeftFill(  lessOrEqualWidthImages[i] , edge ) )
            {
                result = lessOrEqualWidthImages[i]->id;
            }
        }
    }
    return result;
}
/************************************************************************/
/* 
按装入序列对两个矩形进行组合,如果组合后的宽度与最低水平线宽度ek相等,则优先装入组合序列中的首个矩形. 
例如,存在两种组合I(i1).w+I(j1).w=ek.w, I(i2).w+I(j2).w=ek.w, 
如果I(i1)的面积大于I(i2),则首先装入I(i1),否则装入I(i2). 
*/
/************************************************************************/
int LLABF::JointWidthFitFirst( SimpleEdgeInfo* edge )
{
    int result = -1;

    findJointEqualToEdgeWidth(edge);

    if ( joint_EqualWidthImages.size()  > 0 )  // 存在组合和edge的宽度相等的image   然后求矩形面积最大的image  
    {
        int max_area = 0;
        int tmp_area;
        SimpleImageInfo* max_area_image;
        for( int i = 0; i < joint_EqualWidthImages.size(); i++ )
        {
            tmp_area = joint_EqualWidthImages[i]->width * joint_EqualWidthImages[i]->height;
            if ( max_area < tmp_area )
            {
                max_area = tmp_area;
                max_area_image = joint_EqualWidthImages[i];
            }

        }
        result = max_area_image->id;
    }
    return result;
}
/************************************************************************/
/* 
在一定范围内,从待装矩形件中按照装入序列依次查找宽度或高度不大于最低水平线ek宽度的矩形, 
若存在,则将其装入;若存在多个,则装入面积最大的矩形. 
PF可能在最低水平线上产生新的、更小的可装入区域,同时使得较小矩形延迟装入. 
*/
/************************************************************************/
int LLABF::PlaceabelFirst( SimpleEdgeInfo* edge )
{
    int result = -1;

    findLessOrEqualToEdgeWidth(edge);

    if ( lessOrEqualWidthImages.size()  > 0 )  // 存在和edge的宽度相等的image   然后求矩形面积最大的image  
    {
        int max_area = 0;
        int tmp_area;
        SimpleImageInfo* max_area_image;
        for( int i = 0; i < lessOrEqualWidthImages.size(); i++ )
        {
            tmp_area = lessOrEqualWidthImages[i]->width * lessOrEqualWidthImages[i]->height;
            if ( max_area < tmp_area )
            {
                max_area = tmp_area;
                max_area_image = lessOrEqualWidthImages[i];
            }
        }
        result = max_area_image->id;
    }

    return result;
}

void LLABF::findEqualToEdgeWidth( SimpleEdgeInfo * edge )
{
    equalWidthImages.clear();
    for ( int i = 0; i < imageInfos.size(); i++ )
    {
        if ( !imageInfos[i]->isInRect )
        {
            if ( imageInfos[i]->width == edge->width )
            {
                equalWidthImages.push_back(imageInfos[i]);
                imageInfos[i]->isRotate = false;
            }
            else if ( imageInfos[i]->height == edge->width )
            {
                equalWidthImages.push_back(imageInfos[i]);
                imageInfos[i]->isRotate = true;
            }        
        }
    }
}


void LLABF::findLessOrEqualToEdgeWidth( SimpleEdgeInfo * edge )
{
    lessOrEqualWidthImages.clear();
    for ( int i = 0; i < imageInfos.size(); i++ )
    {
        if ( !imageInfos[i]->isInRect )
        {
            if ( imageInfos[i]->width <= edge->width )
            {
                lessOrEqualWidthImages.push_back(imageInfos[i]);
                imageInfos[i]->isRotate = false;
            }
            else if ( imageInfos[i]->height <= edge->width )
            {
                lessOrEqualWidthImages.push_back(imageInfos[i]);
                imageInfos[i]->isRotate = true;
            }   
        }
    }
}

// 按装入序列对两个矩形进行组合,如果组合后的宽度与最低水平线宽度ek相等,则优先装入组合序列中的首个矩形  
void LLABF::findJointEqualToEdgeWidth( SimpleEdgeInfo * edge )
{
    joint_EqualWidthImages.clear();
    int i_image_width = 0;
    int j_image_width = 0;
    int edge_width = edge->width;
    for ( int i = 0; i< imageInfos.size(); i++ )
    {
        if ( !imageInfos[i]->isInRect )
        {
            i_image_width = imageInfos[i]->width;
            j_image_width = edge_width - i_image_width;
            for ( int j = 0; j< imageInfos.size(); j++ )
            {
                if ( !imageInfos[j]->isInRect )
                {
                    if ( j_image_width == imageInfos[j]->width )
                    {
                        joint_EqualWidthImages.push_back(imageInfos[i]);
                    }
                    
                }

            }
        }
    }
}



bool LLABF::canLeftFill( SimpleImageInfo * image, SimpleEdgeInfo * edge )
{
    if ( edge->id == 0 )
    {
        return false;
    }
    int tmpHeight = image->height;
    if ( image->isRotate )
    {
        tmpHeight = image->width;
    }

    if ( tmpHeight == (edgeInfos[ edge->id - 1 ]->y - edge->y)) // 如果 image的高度和ek-1 - ek 的高度差相等,即可实现左填平   
    {
        return true;
    }
    return false;
}

bool LLABF::canRightFill( SimpleImageInfo * image, SimpleEdgeInfo * edge )
{
    if ( edge->id == edgeInfos.size() - 1 )
    {
        return false;
    }
    int tmpHeight = image->height;
    if ( image->isRotate )
    {
        tmpHeight = image->width;
    }
    if ( image->height == (edgeInfos[ edge->id + 1 ]->y - edge->y)) // 如果 image的高度和ek+1 - ek 的高度差相等,即可实现右填平   
    {
        return true;
    }
    return false;
}

int LLABF::getBigImageHeight()
{
    // 在E中选取最低水平线ek   
    int the_highest_y = 0;
    for ( int j = 0 ; j< edgeInfos.size(); j++ )
    {
        if ( the_highest_y < edgeInfos[j]->y )
        {
            the_highest_y = edgeInfos[j]->y;
        }
    } // 选取结束   
    return the_highest_y;
}

里面都有注释,参照着论文应该都能明白。希望以后自己能够看懂。

下面就是根据信息,将小图的数据copy至大图内。小图旋转90度复制像素时,着实被坑了一把,后来方法没怎么改,只是将小图坐标固定,然后根据小图坐标去求大图坐标.

#ifndef __BIG_IMAGE_H__
#define __BIG_IMAGE_H__
#include "cocos2d.h"
#include "HelloWorldScene.h"
class SimpleImageInfo;
class BigImage
{
public :
    BigImage();
    ~BigImage();
    BigImage( std::string bigFileName, int width, int height , std::string filePlist );

    void init();
    void publish();
    void initBigImageWithImagesData( std::vector< SimpleImageInfo* >  * images );
private:
    std::vector<SimpleImageInfo*> * imageInfos;
    int m_dstImageWidth;
    int m_dstImageHeight;
    int m_dataLen;
    int m_dst_ptr;
    int m_dst_pixels_start_ptr;
    unsigned char * m_dstImageData;
    std::string m_bigImageName;
    std::string m_plistName;
};


#endif

#include "BigImage.h"
//#include "cocos2d/external/png/include/win32/png.h"
//#include "cocos2d/cocos/2d/platform/CCImage.h"
#include  <math.h>
#include  <iostream>
USING_NS_CC;

BigImage::BigImage()
    :m_dstImageWidth(0)
    ,m_dstImageHeight(0)
    ,m_dst_ptr(0)
    ,m_dst_pixels_start_ptr(0)
    ,m_dataLen(0)
    ,m_dstImageData(NULL)
    ,m_bigImageName("test.png")
{

}

BigImage::~BigImage()
{
    free(m_dstImageData);
}
BigImage::BigImage( std::string bigFileName, int width, int height , std::string filePlist )
{
    m_bigImageName = bigFileName;
    m_dstImageWidth = width;
    m_dstImageHeight = height;
    m_plistName = filePlist;
    m_dataLen = m_dstImageWidth * m_dstImageHeight * 4;
    this->init();
}

void BigImage::init()
{
    m_dstImageData = (unsigned char* )malloc(m_dstImageWidth * m_dstImageHeight * 4 );
    memset(m_dstImageData,0,m_dataLen);
}

void BigImage::publish()
{
    std::string writablePath = FileUtils::getInstance()->getWritablePath();
    std::string fullPath_imageName = writablePath + m_bigImageName;

    Image * new_image = new Image();
    new_image->initWithRawData( m_dstImageData,m_dataLen ,m_dstImageWidth,m_dstImageHeight,8 );
    new_image->saveToFile( fullPath_imageName,false);

    // plist的根节点  
    Dictionary * dict_root = Dictionary::create();
    // plist下的frames节点 其内部为各个子图的信息节点     
    auto dict_frames = Dictionary::create();
    for ( int i = 0; i< imageInfos->size(); i++ )
    {
        // small image name 
        SimpleImageInfo* srcImage = imageInfos->at(i);
        auto imageDict = Dictionary::create();
        // frame  位置 图片大小   
        auto str_frame = String::createWithFormat("{{%d,%d},{%d,%d}}", srcImage->x,srcImage->y,srcImage->width,srcImage->height );
        imageDict->setObject(str_frame,"frame");
        // offset  
        auto str_offset = String::createWithFormat("{%d,%d}",0,0 );
        imageDict->setObject(str_offset,"offset");
        // is rotated ? 
        auto b_rotate = Bool::create(srcImage->isRotate);
        imageDict->setObject( b_rotate,"rotated");
        // sourceColorRect  
        auto str_colorRect = String::createWithFormat("{{%d,%d},{%d,%d}}", 0,0,srcImage->width,srcImage->height );
        imageDict->setObject(str_colorRect,"sourceColorRect");
        // sourceSize 
        auto str_sourceSize = String::createWithFormat("{%d,%d}", srcImage->width,srcImage->height );
        imageDict->setObject(str_sourceSize,"sourceSize");

        dict_frames->setObject( imageDict,srcImage->imageName );
    }
    
    dict_root->setObject( dict_frames, "frames");

    // 根节点下的元数据节点   
    auto dict_metadata = Dictionary::create();

    auto intObject = Integer::create(2);
    dict_metadata->setObject(intObject,"format");
    
    auto strObjectRealName = String::create(m_bigImageName);
    dict_metadata->setObject(strObjectRealName,"realTextureFileName");
   
    auto strObjectSize = String::createWithFormat("{%d,%d}", m_dstImageWidth, m_dstImageHeight);
    dict_metadata->setObject( strObjectSize, "size");

    auto strObjectTextureFileName = String::create(m_bigImageName);
    dict_metadata->setObject(strObjectTextureFileName,"textureFileName");
   
    dict_root->setObject( dict_metadata,"metadata");

    std::string fullPath_plist = writablePath + m_plistName;
    if(dict_root->writeToFile(fullPath_plist.c_str()))
        log("see the plist file at %s", fullPath_plist.c_str());
    else
        log("write plist file failed");
}

void BigImage::initBigImageWithImagesData( std::vector<SimpleImageInfo*> * images )
{
    imageInfos = images;

    for ( int i = 0; i< images->size(); i++ )
    {
        SimpleImageInfo* srcImageInfo = images->at(i);
        Image * srcImage = new Image();
        srcImage->initWithImageFile( srcImageInfo->imageName );
        unsigned char * srcImageData = srcImage->getData();
        ssize_t dataLen = srcImage->getDataLen();
        int srcImageWidth = srcImage->getWidth();
        int srcImageHeight = srcImage->getHeight();

        int nbitPerPixels = srcImage->getBitPerPixel();

        Point position = Point(srcImageInfo->x,srcImageInfo->y );
        int src_pixels_ptr = 0;

        if ( srcImageInfo->isRotate )
        {
            int target_ptr = ( position.y * m_dstImageWidth + position.x ) * 4;

            src_pixels_ptr =  ( srcImageWidth * srcImageHeight - srcImageWidth ) * 4;

            int target_pixels_start_ptr = target_ptr;
            int src_pixels_start_ptr = src_pixels_ptr;

            int inner_line_dert = m_dstImageWidth * 4; // 每一行的数据   
            int src_line_dert = 4 ;   // 每一行的数据个数    

            for ( int i = 0; i< srcImageWidth; i++ )
            {
                for ( int j = 0; j < srcImageHeight; j++ )
                {
                    m_dstImageData[ target_ptr ] = srcImageData[ src_pixels_ptr ];
                    m_dstImageData[ target_ptr + 1 ] = srcImageData[ src_pixels_ptr + 1 ];
                    m_dstImageData[ target_ptr + 2 ] = srcImageData[ src_pixels_ptr + 2 ];
                    m_dstImageData[ target_ptr + 3 ] = srcImageData[ src_pixels_ptr + 3 ];
                    target_ptr += 4;
                    src_pixels_ptr -= srcImageWidth * 4 ;
                }

                target_pixels_start_ptr += inner_line_dert;
                src_pixels_start_ptr += src_line_dert;

                src_pixels_ptr = src_pixels_start_ptr;
                target_ptr = target_pixels_start_ptr;
            }
        }
        else
        {
            int target_ptr = ( position.y * m_dstImageWidth + position.x ) * 4;

            int src_pixels_start_ptr = src_pixels_ptr;
            int target_pixels_start_ptr = target_ptr;

            int inner_line_dert = m_dstImageWidth * 4; // 每一行的数据   
            int src_line_dert = srcImageWidth * 4;   // 每一行的数据个数    

            for ( int i = 0; i< srcImageHeight; i++ )
            {
                for ( int j = 0; j < srcImageWidth; j++ )
                {
                    m_dstImageData[ target_ptr ] = srcImageData[ src_pixels_ptr ];
                    m_dstImageData[ target_ptr + 1 ] = srcImageData[ src_pixels_ptr + 1 ];
                    m_dstImageData[ target_ptr + 2 ] = srcImageData[ src_pixels_ptr + 2 ];
                    m_dstImageData[ target_ptr + 3 ] = srcImageData[ src_pixels_ptr + 3 ];
                    target_ptr += 4;
                    src_pixels_ptr += 4;
                }

                target_pixels_start_ptr += inner_line_dert;
                src_pixels_start_ptr += src_line_dert;

                src_pixels_ptr = src_pixels_start_ptr;
                target_ptr = target_pixels_start_ptr;
            }

        }

        delete srcImage;
    }
 
}

估计可能是刚开始计算时,是按照左下角是坐标原点计算的。但是,实际上Image的data是以左上角为(0,0)点的。所以尽管方法对,但是因为一个是左手坐标系,一个是右手坐标系,所以才导致错误的。这样看,首先确定坐标原点是多么重要的一件事。LLABF算法,还没有texturepacker采用的maxRects算法优。但是结果还算是可以吧,加上人的智慧,其实也可以合出不错的图。大图的高度是根据LLABF算法返回的数据确定的,没有取2的整数次幂。想取的可以取。感觉不太必要。


关于Image Engineering& Computer Vision更多讨论与交流,敬请关注本博客和新浪微博songzi_tea






你可能感兴趣的:(2-D矩形装箱问题)