Skip to content

Commit

Permalink
use a better packing algorithm for image/glyph atlas
Browse files Browse the repository at this point in the history
  • Loading branch information
mourner committed Aug 22, 2018
1 parent 754ec0b commit 46fa816
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 12 deletions.
9 changes: 3 additions & 6 deletions src/render/glyph_atlas.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// @flow

import ShelfPack from '@mapbox/shelf-pack';

import { AlphaImage } from '../util/image';
import { register } from '../util/web_worker_transfer';
import binpack from '../util/binpack';

import type {GlyphMetrics, StyleGlyph} from '../style/style_glyph';

Expand All @@ -27,7 +26,6 @@ export default class GlyphAtlas {

constructor(stacks: { [string]: { [number]: ?StyleGlyph } }) {
const positions = {};
const pack = new ShelfPack(0, 0, {autoResize: true});
const bins = [];

for (const stack in stacks) {
Expand All @@ -49,9 +47,8 @@ export default class GlyphAtlas {
}
}

pack.pack(bins, {inPlace: true});

const image = new AlphaImage({width: pack.w, height: pack.h});
const {w, h} = binpack(bins);
const image = new AlphaImage({width: w, height: h});

for (const stack in stacks) {
const glyphs = stacks[stack];
Expand Down
9 changes: 3 additions & 6 deletions src/render/image_atlas.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// @flow

import ShelfPack from '@mapbox/shelf-pack';

import { RGBAImage } from '../util/image';
import { register } from '../util/web_worker_transfer';
import binpack from '../util/binpack';

import type {StyleImage} from '../style/style_image';

Expand Down Expand Up @@ -53,7 +52,6 @@ export default class ImageAtlas {

constructor(images: {[string]: StyleImage}) {
const positions = {};
const pack = new ShelfPack(0, 0, {autoResize: true});
const bins = [];

for (const id in images) {
Expand All @@ -68,9 +66,8 @@ export default class ImageAtlas {
positions[id] = new ImagePosition(bin, src);
}

pack.pack(bins, {inPlace: true});

const image = new RGBAImage({width: pack.w, height: pack.h});
const {w, h} = binpack(bins);
const image = new RGBAImage({width: w, height: h});

for (const id in images) {
const src = images[id];
Expand Down
88 changes: 88 additions & 0 deletions src/util/binpack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// @flow

export default function binpack(boxes: Array<{w: number, h: number, x?: number, y?: number}>) {
let area = 0;
let maxWidth = 0;

for (const box of boxes) {
area += box.w * box.h;
maxWidth = Math.max(maxWidth, box.w);
}

// sort the boxes for insertion by height, descending
boxes.sort((a, b) => b.h - a.h);

// aim for a squarish resulting container,
// slightly adjusted for sub-100% space utilization
const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);

// start with a single empty space, unbounded at the bottom
const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}];

let width = 0;
let height = 0;

for (const box of boxes) {
for (let i = spaces.length - 1; i >= 0; i--) {
const space = spaces[i];

// look for empty spaces that can accommodate the current box
if (box.w > space.w || box.h > space.h) continue;

// found the space; add the box to its top-left corner
// |-------|-------|
// | box | |
// |_______| |
// | space |
// |_______________|
box.x = space.x;
box.y = space.y;

height = Math.max(height, box.y + box.h);
width = Math.max(width, box.x + box.w);

// if the space matches the box exactly, just remove it
if (box.w === space.w && box.h === space.h) {
const last = spaces.pop();
if (i < spaces.length) spaces[i] = last;

// if it matches the box height, update the space accordingly
// |-------|---------------|
// | box | updated space |
// |_______|_______________|
} else if (box.h === space.h) {
space.x += box.w;
space.w -= box.w;

// if it matches the box width, update the space accordingly
// |---------------|
// | box |
// |_______________|
// | updated space |
// |_______________|
} else if (box.w === space.w) {
space.y += box.h;
space.h -= box.h;

// otherwise, update the space accordingly and a new one
// |-------|-----------|
// | box | new space |
// |_______|___________|
// | updated space |
// |___________________|
} else {
spaces.push({
x: space.x + box.w,
y: space.y,
w: space.w - box.w,
h: box.h
});
space.y += box.h;
space.h -= box.h;
}
break;
}
}

return {w: width, h: height};
}

0 comments on commit 46fa816

Please sign in to comment.