Skip to content

Commit

Permalink
Add generic type input with width/height (#6)
Browse files Browse the repository at this point in the history
- Add generic type input with width/height
- Add test for the generic type
- Add bundled umd/es js package and d.ts in dist folder
- Updated README
- Add TODO
  • Loading branch information
soimy authored May 26, 2019
1 parent c1a140e commit ce30df4
Show file tree
Hide file tree
Showing 57 changed files with 6,020 additions and 1,331 deletions.
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,30 @@ typings/
# dotenv environment variables file
.env

# Mac stuff
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ language: node_js
node_js:
- "stable"

before_install:
- npm update

install:
- npm install

Expand All @@ -14,5 +17,5 @@ script:
- npm run cover

# Send coverage data to Coveralls
after_script: "cat coverage/lcov.info | node_modules/coveralls/bin/coveralls.js"
after_script: "cat test/coverage/lcov.info | node_modules/coveralls/bin/coveralls.js"

27 changes: 3 additions & 24 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,13 @@
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"name": "Jest Tests",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": [
"-u",
"bdd",
"--timeout",
"10000",
"--colors",
"${workspaceFolder}/test"
"--runInBand",
],
"preLaunchTask": "build",
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "TS:Mocha Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"--compilers",
"ts:ts-node/register",
"--timeout",
"10000",
"--colors",
"${workspaceRoot}/test/*.spec.ts"
// "${workspaceRoot}/test/maxrects_bin.spec.js"
],
"internalConsoleOptions": "openOnSessionStart"
}
]
}
8 changes: 3 additions & 5 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build:clean",
"label": "build",
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": [
"$tsc"
],
"problemMatcher": [ "$tsc" ],
"group": {
"kind": "build",
"isDefault": true
Expand Down
48 changes: 35 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Max Rects Packer

[![Build Status](https://travis-ci.org/soimy/maxrects-packer.svg?branch=master)](https://travis-ci.org/soimy/maxrects-packer)
[![Coverage Status](https://coveralls.io/repos/github/soimy/maxrects-packer/badge.svg?branch=master)](https://coveralls.io/github/soimy/maxrects-packer?branch=master)
[![npm version](https://badge.fury.io/js/maxrects-packer.svg)](https://badge.fury.io/js/maxrects-packer)
![npm](https://img.shields.io/npm/dm/maxrects-packer.svg)
![npm type definitions](https://shields-staging.herokuapp.com/npm/types/maxrects-packer.svg)

A simple max rectangle 2d bin packing algorithm for packing glyphs or images into multiple sprite-sheet/atlas. Minimalist with no module dependency.

Expand All @@ -14,13 +16,18 @@ And you can also save/load to reuse packer to add new sprites. (Below is a demo

![Preview image](https://raw.githubusercontent.com/soimy/maxrects-packer/master/preview.png)

## Usage:
**Notice:** *Since version 2.0.0beta Max Rects Packer is rewritten in `typescript` and change the import method from old `require("maxrects-packer")` to `require("maxrects-packer").MaxRectsPacker` or more fashioned `import` statement.*
## Installing

```bash
npm install maxrects-packer --save
```

## Usage

**Note:** *Since version 2.0.0 Max Rects Packer is rewritten in `typescript` and change the import method from old `require("maxrects-packer")` to `require("maxrects-packer").MaxRectsPacker` or more fashioned `import` statement.*

**Note:** *Since version 2.1.0 packer can be fed with any object with `width & height` members, no need to follow `{ width: number, height: number, data: any }` pattern, if you are using `typescript`, that also mean any class extending `MaxRectsPacker.IRectangle`*

```javascript
let MaxRectsPacker = require("maxrects-packer").MaxRectsPacker;
const options = {
Expand All @@ -31,29 +38,29 @@ const options = {
}; // Set packing options
let packer = new MaxRectsPacker(1024, 1024, 2, options); // width, height, padding, options

let input = [
{width: 600, height: 20, data: {name: "tree", foo: "bar"}},
{width: 600, height: 20, data: {name: "flower"}},
{width: 2000, height: 2000, data: {name: "oversized background"}},
{width: 1000, height: 1000, data: {name: "background"}},
{width: 1000, height: 1000, data: {name: "overlay"}}
let input = [ // any object with width & height is OK since v2.1.0
{width: 600, height: 20, name: "tree", foo: "bar"},
{width: 600, height: 20, name: "flower"},
{width: 2000, height: 2000, name: "oversized background", {frameWidth: 500, frameHeight: 500}},
{width: 1000, height: 1000, name: "background", color: 0x000000ff},
{width: 1000, height: 1000, name: "overlay"}
];

packer.addArray(input); // Start packing with input array
packer.bins.forEach(bin => {
console.log(bin.rects);
});

// Reuse packer
// Reuse packer
let bins = packer.save();
packer.load(bins);
packer.addArray(input);


```

## Test
```

```bash
npm test
```

Expand All @@ -62,35 +69,50 @@ npm test
Note: maxrects-packer requires node >= 4.0.0

#### ```new MaxRectsPacker(maxWidth, maxHeight[, padding, options])```

Creates a new Packer. maxWidth and maxHeight are passed on to all bins. If ```padding``` is supplied all rects will be kept at least ```padding``` pixels apart.

- `options.smart` packing with smallest possible size. (default is `true`)
- `options.pot` bin size round up to smallest power of 2. (defalt is `true`)
- `options.square` bin size shall alway be square. (defaut is `false`)
- `options.allowRotation` allow 90-degree rotation while packing. (defaut is `false`)

#### ```packer.add(width, height, data)```
#### ```packer.add(width, height, data)``` +1 overload

Adds a rect to an existing bin or creates a new one to accomodate it. ```data``` can be anything, it will be stored along with the position data of each rect.

#### ```packer.addArray([{width: width, height: height, data: data}, ...])```
#### ```packer.add({width: number, height: number, ... })``` +1 overload

Adds a rect to an existing bin or creates a new one to accomodate it. Accept any object with `width & height`. If you are using `typescript`, that means any class extends `MaxRectsPacker.IRectangle`

#### ```packer.addArray([{width: number, height: number, ...}, ...])```

Adds multiple rects. Since the input is automatically sorted before adding this approach usually leads to fewer bins created than separate calls to ```.add()```

#### ```let bins = packer.save()```

Save current bins settings and free area to an Array of objects for later use. Better to `JSON.stringify(bins)` and store in file.

#### ```packer.load(bins)```

Restore previous saved `let bins = JSON.parse(fs.readFileSync(savedFile, 'utf8'));` settings and overwrite current one. Continue packing and previous packed area will not be overlaped.

#### ```packer.bins```

Array of bins. Every bin has a ```width``` and ```height``` parameter as well as an array ```rects```.

#### ```packer.bins[n].rects```

Array of rects for a specific bin. Every rect has ```x```, ```y```, ```width```, ```height```, ```rot``` and ```data```. In case of an rect exceeding ```maxWidth```/```maxHeight``` there will also be an ```oversized``` flag set to ```true```.

## Support for 90-degree rotation packing

If `options.allowRotation` is set to `true`, packer will attempt to do an extra test in `findNode()` on rotated `Rectangle`. If the rotated one gives the best score, the given `Rectangle` will be rotated in the `Rectangle.rot` set to `true`.

## Support for oversized rectangles

Normally all bins are of equal size or smaller than ```maxWidth```/```maxHeight```. If a rect is added that individually does not fit into those constraints a special bin will be created. This bin will only contain a single rect with a special "oversized" flag. This can be handled further on in the chain by displaying an error/warning or by simply ignoring it.

## Packing algorithm

Use Max Rectangle Algorithm for packing, same as famous **Texture Packer**
7 changes: 7 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
✔ 用Rollup一体化打包 @done(19-05-26 14:45)
✔ 测试引擎Mocha->Jest @done(19-05-26 14:45)
✔ 更新Covers 从Istanble->Jest @done(19-05-26 14:45)
✔ 更新README.md @done(19-05-26 17:34)
✔ Test增加Generic type部分 @done(19-05-26 17:04)
☐ 发布2.1.0版
☐ 增加Bin.border控制参数
149 changes: 149 additions & 0 deletions dist/maxrects-packer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@

// FILE GENERATED BY `[email protected]`
// https://github.com/Swatinem/rollup-plugin-dts

interface IRectangle {
width: number;
height: number;
x: number;
y: number;
[propName: string]: any;
}
declare class Rectangle implements IRectangle {
width: number;
height: number;
x: number;
y: number;
rot: boolean;
data: any;
oversized: boolean;
constructor(width?: number, height?: number, x?: number, y?: number, rot?: boolean);
static Collide(first: Rectangle, second: Rectangle): boolean;
static Contain(first: Rectangle, second: Rectangle): boolean;
area(): number;
collide(rect: Rectangle): boolean;
contain(rect: Rectangle): boolean;
}

interface IBin {
width: number;
height: number;
maxWidth: number;
maxHeight: number;
freeRects: IRectangle[];
rects: IRectangle[];
options: IOption;
}
declare abstract class Bin implements IBin {
width: number;
height: number;
maxWidth: number;
maxHeight: number;
freeRects: IRectangle[];
rects: IRectangle[];
options: IOption;
abstract add(rect: IRectangle): IRectangle | undefined;
abstract add(width: number, height: number, data: any): IRectangle | undefined;
}

/**
* Options for MaxRect Packer
* @property {boolean} options.smart Smart sizing packer (default is true)
* @property {boolean} options.pot use power of 2 sizing (default is true)
* @property {boolean} options.square use square size (default is false)
* @export
* @interface Option
*/
interface IOption {
smart?: boolean;
pot?: boolean;
square?: boolean;
allowRotation?: boolean;
}
declare class MaxRectsPacker<T extends IRectangle = Rectangle> {
width: number;
height: number;
padding: number;
options: IOption;
bins: Bin[];
/**
* Creates an instance of MaxRectsPacker.
* @param {number} width of the output atlas (default is 4096)
* @param {number} height of the output atlas (default is 4096)
* @param {number} padding between glyphs/images (default is 0)
* @param {IOption} [options={}] (Optional) packing options
* @memberof MaxRectsPacker
*/
constructor(width?: number, height?: number, padding?: number, options?: IOption);
/**
* Add a bin/rectangle object with data to packer
* @param {number} width of the input bin/rectangle
* @param {number} height of the input bin/rectangle
* @param {*} data custom data object
* @memberof MaxRectsPacker
*/
add(width: number, height: number, data: any): IRectangle;
/**
* Add a bin/rectangle object extends IRectangle to packer
* @template T Generic type extends IRectangle interface
* @param {T} rect the rect object add to the packer bin
* @memberof MaxRectsPacker
*/
add(rect: T): T;
/**
* Add an Array of bins/rectangles to the packer.
* Object structure: { width, height, data }
* @param {IRectangle[]} rects Array of bin/rectangles
* @memberof MaxRectsPacker
*/
addArray(rects: T[]): void;
/**
* Load bins to the packer, overwrite exist bins
* @param {MaxRectsBin[]} bins MaxRectsBin objects
* @memberof MaxRectsPacker
*/
load(bins: Bin[]): void;
/**
* Output current bins to save
* @memberof MaxRectsPacker
*/
save(): IBin[];
private sort;
}

declare class MaxRectsBin<T extends IRectangle = Rectangle> extends Bin {
maxWidth: number;
maxHeight: number;
padding: number;
options: IOption;
width: number;
height: number;
freeRects: Rectangle[];
rects: IRectangle[];
private verticalExpand;
private stage;
constructor(maxWidth?: number, maxHeight?: number, padding?: number, options?: IOption);
add(rect: T): T | undefined;
add(width: number, height: number, data: any): Rectangle | undefined;
private findNode;
private splitNode;
private pruneFreeList;
private updateBinSize;
private expandFreeRects;
}

declare class OversizedElementBin<T extends IRectangle = Rectangle> extends Bin {
width: number;
height: number;
data: any;
maxWidth: number;
maxHeight: number;
options: IOption;
rects: T[];
freeRects: IRectangle[];
constructor(rect: T);
constructor(width: number, height: number, data: any);
add(): undefined;
}

export { Bin, IOption, IRectangle, MaxRectsBin, MaxRectsPacker, OversizedElementBin, Rectangle };
Loading

0 comments on commit ce30df4

Please sign in to comment.