首页 > 代码库 > 拼图算法,将零碎小图,整理到一张大图之上,自动合并。二叉树实现

拼图算法,将零碎小图,整理到一张大图之上,自动合并。二叉树实现

需求描述

    在开发需求中,有种场景:工程中图片太多,很多png图片,导致程序包很大。尤其是出现在移动平台iOS,Android和游戏平台。这是因为png的头占用资源比较大,如果将所有png图片拼接到一起,就省了png的头部信息;那么程序包应该会少不小!

    想法很好!如果程序中有四五百个png图片,手动合成到大图上恐怕不是小工作量。何况每天的资源图片都有可能更新,更新一个就要重新拼图。这个工作量肯定不是人能搞定的了。可不可让计算机帮助我们来拼图。拼图完成后,计算机告诉我们每个图片的坐标,程序运行时候我们根据坐标去拿图片就行了。

文章最后可下载demo

想想,手动拼图的第一步。

    首先设定一个很大的空白图片,假如是(1000,1000)这样就够装很多图片。考虑到最大限度利用该空白图片。事先将所有图片的大小排序。最大在最前面。

    将最大的图片放到空白图片左上角,如下图所示:那么可以将剩余的区域分为两部分:left和right部分,用于放后继图片。

设计数据模型——二叉树

    从刚刚的图片中,很容易想到二叉树的数据结构,如下图所示


看懂这个图片,主要通过三个● 和三块区域

采用二叉树数据结构

按照上图的思路,采用二叉树数据结构。怎么将图片一个个插入到二叉树中呢?有一下几种方法:
● 前序遍历
● 后序遍历
● 中序遍历
● 层序遍历

实现初始化一个数组,存储已经排序好了的图片资源
    NSMutableArray* tmp = [NSMutableArray array];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(120, 100) name:@"1.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(120, 100) name:@"2.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(100, 100) name:@"3.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(80, 21) name:@"4.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(70, 40) name:@"5.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(65, 30) name:@"6.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(50, 21) name:@"7.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(50, 21) name:@"8.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(50, 21) name:@"9.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(50, 21) name:@"10.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(40, 40) name:@"11.png"]];
    [tmp addObject:[[picNode alloc]initWithSize:CGSizeMake(35, 30) name:@"12.png"]];

看一下效果GIF图片,然后再说一下insertPicture的代码。



看看insertPicture的逻辑:
-(bool) insertPicture:(BTreeNode*)node{
    
    //如果图片大小,可以放下这个rect。那么就设置一下这个rect的大小为图片大小,剩余面积分为left和right
    if (!node.isFull
        && node.virtualPic.size.width >= _currentPic.size.width
        && node.virtualPic.size.height >= _currentPic.size.height) {
        node.isFull = TRUE;
        /*
         拆分剩余rect为left和right两个分支。
         ●---------●-----------------
         | picture |   right        |
         |         |                |
         ●---------------------------
         |                          |
         |        left              |
         |                          |
         |                          |
         |                          |
         ----------------------------
         例如.picture已经占据了【左上角】区域。剩下的区域分为left和right。点(●)的地方就是CGPoint了。
         ● 优化点:如果picture是矩形,那么在生成left和right的时候。可以有两个选择:向下延伸,向右延伸。
         通常是向值小的一方延伸,这样保证值大的一方可以放进去更多的图片。
         ● 上面的例子是向右延伸。
         */
        node.left = [[BTreeNode alloc]init];
        node.left.point = CGPointMake(node.point.x, node.point.y + _currentPic.size.height);
        node.left.virtualPic = [[picNode alloc]init];
        
        node.right = [[BTreeNode alloc]init];
        node.right.point = CGPointMake(node.point.x+ _currentPic.size.width, node.point.y);
        node.right.virtualPic = [[picNode alloc]init];
        
        //优化一下
        if (_currentPic.size.width >= _currentPic.size.height)
        {
            //left的宽度是parent的宽度。高度是parent高度 - 图片的高度
            node.left.virtualPic.size = CGSizeMake(node.virtualPic.size.width, node.virtualPic.size.height - _currentPic.size.height);
            //right的宽度是,parent的宽度-图片的宽度。高度是图片的高度。
            node.right.virtualPic.size = CGSizeMake(node.virtualPic.size.width- _currentPic.size.width, _currentPic.size.height);
        }else{
            //left的宽度是图片的宽度。高度是parent高度 - 图片的高度(不变)
            node.left.virtualPic.size = CGSizeMake(_currentPic.size.width, node.virtualPic.size.height - _currentPic.size.height);
            //right的宽度是,parent的宽度-图片的宽度。高度parent的高度。
            node.right.virtualPic.size = CGSizeMake(node.virtualPic.size.width- _currentPic.size.width, node.virtualPic.size.height);
        }
        
        //将虚拟的pic复制
        node.virtualPic = [_currentPic copy];
    return YES;
    }
    return NO;
}

总结:

后序遍历的图片利用率最高,这样的结果也符合人类的思维,假如我们手动排班。那么应该也是最大在上角,此大的往后排。后侧排满后再从左侧开始,即蛇型曲线。

demo下载地方点击打开链接