@image/packer
Version:
image packer
131 lines (113 loc) • 3.54 kB
text/typescript
export interface ISize {
width: number,
height: number
}
export interface IRect {
x: number;
y: number;
width: number;
height: number,
data: ISize;
}
export interface INode {
x: number;
y: number;
width: number;
height: number,
children?: INode[]
}
export class MultiBinPacker {
public readonly maxWidth: number;
public readonly maxHeight: number;
public readonly padding?: number;
public readonly bins: BinaryTreeBin[] = [];
public readonly oversizedElements: ISize[] = [];
constructor(maxWidth: number, maxHeight: number, padding?: number) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.padding = padding;
}
public add(data: ISize) {
if (data.width > this.maxWidth || data.height > this.maxHeight) {
this.oversizedElements.push(data);
} else {
const added = this.bins.find(bin => !!bin.add(data));
if (!added) {
const bin = new BinaryTreeBin(this.maxWidth, this.maxHeight, this.padding);
bin.add(data);
this.bins.push(bin);
}
}
}
private sort(rects: ISize[]) {
return rects.slice().sort((a, b) => Math.max(b.width, b.height) - Math.max(a.width, a.height));
}
public addArray(rects: ISize[]) {
this.sort(rects).forEach(rect => this.add(rect));
}
}
export class BinaryTreeBin {
private maxWidth: number;
private maxHeight: number;
private padding: number;
private rootNode: INode;
public width: number;
public height: number;
public rects: IRect[];
constructor(maxWidth: number, maxHeight: number, padding?: number) {
this.width = 0;
this.height = 0;
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.padding = padding || 0;
this.rootNode = {x: 0, y: 0, width: maxWidth + this.padding, height: maxHeight + this.padding};
this.rects = [];
}
public add(data: ISize): IRect | undefined {
const node = this.findNode(this.rootNode, data.width + this.padding, data.height + this.padding);
if (node) {
node.children = this.createChildren(node, data.width + this.padding, data.height + this.padding);
this.width = Math.max(this.width, node.x + data.width);
this.height = Math.max(this.height, node.y + data.height);
const rect: IRect = {width: data.width, height: data.height, x: node.x, y: node.y, data};
this.rects.push(rect);
return rect;
}
return undefined;
}
private findNode(node: INode, width: number, height: number): INode | undefined {
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
const found = this.findNode(node.children[i], width, height);
if (found) {
return found;
}
}
return undefined;
}
if ((width <= node.width) && (height <= node.height)) {
return node;
}
return undefined;
}
private createChildren(node: INode, width: number, height: number): INode[] {
const children = [];
if (node.height - height > 0 && node.x < this.maxWidth) {
children.push({
x: node.x,
y: node.y + height,
width: node.width,
height: node.height - height
});
}
if (node.width - width > 0 && node.y < this.maxHeight) {
children.push({
x: node.x + width,
y: node.y,
width: node.width - width,
height: height
});
}
return children;
}
}