图片墙特效和 Bin Packing
截图
预览:http://ambar.no.de/demos/binpacking/index.html
关于
Wiki:Bin packing problem
Demo: Bin Packing
我看到原文是 lightmap 拼接,还有 CSS Sprite 自动化处理,有了这些印象之后,我想图片展示也是用途之一。
装箱问题我想大概是两类:
- 提供一个大箱子,还有很多小箱子。每个小箱子的大小不一样,如何尽可能多的放进大箱子中。
- 不限定大箱子大小,寻找一个最合适尺寸的大箱子,把一批小箱子装起来。
如何做
数据结构定义:每一块空间做为一个节点,节点的值定义为一个矩形。
- 根节点定义为整个容器
- 每一次分区检查条件,根据目标节点的大小,再划分出两个节点,即目标节点的左右分支。
- 不断重复二,形成一颗树。
当要插入一个新的块的时,查找位置就到整个树中寻找匹配的节点。
每一个块也定义为一个矩形,所有的块定义为一个列表:
blocks = [
{ w: 300, h: 390 },
{ w: 150, h: 195 },
{ w: 300, h: 390 },
]
取出要插入的块时,有很多选择:
1) 宽度优先 2) 高度优先 3) 面积优先 4) 大边优先 5) 随机
根据应用的不同来决定了处理的优先顺序,而不同的顺序可以采用不同的分区策略。
我要做的是图片墙,因此选择了随机顺序。这样在刷新页面之后显得比较有新意。
分区策略
我观察了三种不同分区的算法:
-
simple-packer
目标恰好排除到节点分区之外
目标方块区域不计入节点。这里,只插入一个块时,分区只进行一次,packer 树上存在三个节点。图示: -
growing-packer
把首个插入的方块当做根节点,然后往它的下方或右方自适应增长。
它不适用于随机生成顺序的情况,大方块在后面的的话,排列时直接被丢弃了。图示: -
bin-packer
直到给目标划分了节点
上面参照 blackpawn 的实现,每一个插入的方块必然被一个节点包含;每一次分区就进行预判,决定横向还是纵向分区。
因此当第一个方块插入时就已经存在五个节点了。图示:
横向
纵向
代码
可视化
不能图形化的东西是很难了解的,因此我加了两个可视化效果来增强理解。
可视化有两部分:1) 树的形状; 2) 高亮分区块的动画。分别点击 示例 中的 "draw tree" 和 "highlight block" 按钮。
高亮动画
递归高亮分区顺序中的每个节点,使用 Jscex 能简单地在中途模拟 sleep 效果;高亮按钮每点击一次将使高亮速度翻倍。
绘制树
绘制工具我用的 Raphaël ,它在主流浏览器使用 SVG 绘制图形,低版本 IE 中自动切换 VML 绘制图形。
绘制办法我想出了两种:
- 一是横向平铺。遍历树时记住每一层节点的数量再平均分布。这种方式占屏幕空间比较小,图示加了点干扰:
- 二是纵向平铺。绘制目标节点时,计算出所有后代节点的数量,再根据数量和父节点偏移目标。
注:树的每个节点击将高亮对应的块。
验证
对这个特定示例测试时,由于三种模式策略完全不同,它们排列时达到完美效果的概率也不同,尝试10万次运行统计如下:
Mode 耗时 完美次数/尝试次数
bin-packer 947ms 93598/100000
growing-packer 993ms 29349/100000
simple-packer 753ms 25672/100000
为了提高成功机率,可以加点改进。由于我的示例是人工定义好了块的数量,因此可以定义一个模式:
patterns = [
{ blocks:['300x390x2','150x195x10'], w: 900, h: 585 }
]
如果发生图片切换时,图片大小和数量正好匹配了这个模式,就让机器重复去尝试布局吧!
假定概率是 .93598 完美排列,看看 10 人亿次访问时,小于 1 人查看排列失败的情况,要至多重试多少次呢?
var prop = function(people,failed,p0) {
var times = 0, p, f;
while(++times){
p = Math.pow(1-p0,times)
if( Math.round( f = people * p ) <= failed )
return [times,p0,1-p,f]
}
}
// => [8, 0.93598, 0.9999999997178206, 0.2821794342917953]
console.log( prop(1e9,1,.93598) )
结果是,只要至多尝试 8 次,这点时间对机器是微不足道的。
总结
多多观察,找出各种问题间的联系,发现相似的解决办法,一点点付出就可以得到梦幻般的图片墙展示效果。
示例中还尝试了一些合适的技术,有益于快速地测试和验证。