图片排版算法

图片排版算法

  • 前言
  • 算法流程
  • 相关代码
  • 参考

前言

  • 现在很多网站上使用的图标都是从一张大图片里面定位出来的, 这种大图又常常称之为雪碧图, 这样用一张图片就代替了所有的小图片确实很方便。但是雪碧图如果过大, 会导致站点 加载速度很慢, 如果排版之后导致雪碧图间隙利用率很低则会影响到用户体验。同时, 排版算法目前还没有证明存在最优的解法, 至少数学上无法证明, 只能无限趋近于最优, 下面简单介绍下图片排版算法。

算法流程

  1. 将所有图片排序, 优先按照图片高度排序, 如果高度相同则按照宽度排序(也可以按照图片宽高的最大值排序,不过大多数场景下我这种更好一些)
  2. 将排序后的第一张图片作为根节点, 插入图片区域
  3. 插入一张图之后一定会有两个区域, 即right区域和down区域, 这样就能使用二叉树数据结构进行递归了
  4. 每次插入一张图为了使得整体更加方形, 需要判断往右边插还是下面插,具体方法有很多,下面有代码。
  5. 如果图片之间有间距, 需要将最终的图片进行修剪

相关代码

  • 可以直接参考这里
  • 笔者将相关核心代码copy出来, 方便阅读
GrowingPacker.prototype = {

	fit: function(blocks) {
		var n, node, block, len = blocks.length, fit;
		var width  = len > 0 ? blocks[0].width : 0;
		var height = len > 0 ? blocks[0].height : 0;
		this.root = { x: 0, y: 0, width: width, height: height };
		for (n = 0; n < len ; n++) {
			block = blocks[n];
			if (node = this.findNode(this.root, block.width, block.height)) {
				fit = this.splitNode(node, block.width, block.height);
				block.x = fit.x;
				block.y = fit.y;
			}
			else {
				fit = this.growNode(block.width, block.height);
				block.x = fit.x;
				block.y = fit.y;
			}
		}
	},

	findNode: function(root, width, height) {
		if (root.used)
			return this.findNode(root.right, width, height) || this.findNode(root.down, width, height);
		else if ((width <= root.width) && (height <= root.height))
			return root;
		else
			return null;
	},

	splitNode: function(node, width, height) {
		node.used = true;
		node.down  = { x: node.x,         y: node.y + height, width: node.width,         height: node.height - height };
		node.right = { x: node.x + width, y: node.y,          width: node.width - width, height: height               };
		return node;
	},

	growNode: function(width, height) {
		var canGrowDown  = (width  <= this.root.width);
		var canGrowRight = (height <= this.root.height);

		var shouldGrowRight = canGrowRight && (this.root.height >= (this.root.width  + width )); // attempt to keep square-ish by growing right when height is much greater than width
		var shouldGrowDown  = canGrowDown  && (this.root.width  >= (this.root.height + height)); // attempt to keep square-ish by growing down  when width  is much greater than height

		if (shouldGrowRight)
			return this.growRight(width, height);
		else if (shouldGrowDown)
			return this.growDown(width, height);
		else if (canGrowRight)
			return this.growRight(width, height);
		else if (canGrowDown)
			return this.growDown(width, height);
		else
			return null; // need to ensure sensible root starting size to avoid this happening
	},

	growRight: function(width, height) {
		this.root = {
			used: true,
			x: 0,
			y: 0,
			width: this.root.width + width,
			height: this.root.height,
			down: this.root,
			right: { x: this.root.width, y: 0, width: width, height: this.root.height }
		};
		var node;
		if (node = this.findNode(this.root, width, height))
			return this.splitNode(node, width, height);
		else
			return null;
	},

	growDown: function(width, height) {
		this.root = {
			used: true,
			x: 0,
			y: 0,
			width: this.root.width,
			height: this.root.height + height,
			down:  { x: 0, y: this.root.height, width: this.root.width, height: height },
			right: this.root
		};
		var node;
		if (node = this.findNode(this.root, width, height))
			return this.splitNode(node, width, height);
		else
			return null;
	}

};

参考

[1] bin-pack算法

[2] 背包算法

你可能感兴趣的:(算法)