Skip to content

Commit

Permalink
Improve image/glyph atlas packing algorithm (#7171)
Browse files Browse the repository at this point in the history
* use a better packing algorithm for image/glyph atlas

* make sure the atlas is non-zero to fix render tests

* switch to the external potpack package

* replace shelf-pack with potpack in ImageManager
  • Loading branch information
mourner authored Aug 29, 2018
1 parent 99873b8 commit 8dd624d
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 85 deletions.
12 changes: 12 additions & 0 deletions flow-typed/potpack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
declare module "potpack" {
declare type Bin = {
x: number,
y: number,
w: number,
h: number
};

declare function potpack(bins: Array<Bin>): {w: number, h: number, fill: number};

declare module.exports: typeof potpack;
}
25 changes: 0 additions & 25 deletions flow-typed/shelf-pack.js

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
"@mapbox/mapbox-gl-supported": "^1.4.0",
"@mapbox/point-geometry": "^0.1.0",
"@mapbox/shelf-pack": "^3.2.0",
"@mapbox/tiny-sdf": "^1.1.0",
"@mapbox/unitbezier": "^0.0.0",
"@mapbox/vector-tile": "^1.3.1",
Expand Down Expand Up @@ -75,6 +74,7 @@
"pngjs": "^3.0.0",
"postcss-cli": "^5.0.0",
"postcss-inline-svg": "^3.1.1",
"potpack": "^1.0.1",
"pretty-bytes": "^5.1.0",
"prismjs": "^1.8.1",
"prop-types": "^15.6.0",
Expand Down
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 potpack from 'potpack';

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} = potpack(bins);
const image = new AlphaImage({width: w, height: h});

for (const stack in stacks) {
const glyphs = stacks[stack];
Expand Down
8 changes: 3 additions & 5 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 potpack from 'potpack';

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

Expand Down Expand Up @@ -61,7 +60,6 @@ export default class ImageAtlas {
constructor(icons: {[string]: StyleImage}, patterns: {[string]: StyleImage}) {
const iconPositions = {}, patternPositions = {};

const pack = new ShelfPack(0, 0, {autoResize: true});
const bins = [];
for (const id in icons) {
const src = icons[id];
Expand All @@ -87,9 +85,9 @@ export default class ImageAtlas {
patternPositions[id] = new ImagePosition(bin, src);
}

pack.pack(bins, {inPlace: true});
const {w, h} = potpack(bins);
const image = new RGBAImage({width: w || 1, height: h || 1});

const image = new RGBAImage({width: pack.w, height: pack.h});
for (const id in icons) {
const src = icons[id];
const bin = iconPositions[id].paddedRect;
Expand Down
87 changes: 43 additions & 44 deletions src/render/image_manager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

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

import { RGBAImage } from '../util/image';
import { ImagePosition } from './image_atlas';
Expand All @@ -9,7 +9,7 @@ import assert from 'assert';

import type {StyleImage} from '../style/style_image';
import type Context from '../gl/context';
import type {Bin} from '@mapbox/shelf-pack';
import type {Bin} from 'potpack';
import type {Callback} from '../types/callback';

type Pattern = {
Expand Down Expand Up @@ -38,7 +38,6 @@ class ImageManager {
loaded: boolean;
requestors: Array<{ids: Array<string>, callback: Callback<{[string]: StyleImage}>}>;

shelfPack: ShelfPack;
patterns: {[string]: Pattern};
atlasImage: RGBAImage;
atlasTexture: ?Texture;
Expand All @@ -49,9 +48,8 @@ class ImageManager {
this.loaded = false;
this.requestors = [];

this.shelfPack = new ShelfPack(64, 64, {autoResize: true});
this.patterns = {};
this.atlasImage = new RGBAImage({width: 64, height: 64});
this.atlasImage = new RGBAImage({width: 1, height: 1});
this.dirty = true;
}

Expand Down Expand Up @@ -86,12 +84,7 @@ class ImageManager {
removeImage(id: string) {
assert(this.images[id]);
delete this.images[id];

const pattern = this.patterns[id];
if (pattern) {
this.shelfPack.unref(pattern.bin);
delete this.patterns[id];
}
delete this.patterns[id];
}

listImages(): Array<string> {
Expand Down Expand Up @@ -139,10 +132,8 @@ class ImageManager {
// Pattern stuff

getPixelSize() {
return {
width: this.shelfPack.w,
height: this.shelfPack.h
};
const {width, height} = this.atlasImage;
return {width, height};
}

getPattern(id: string): ?ImagePosition {
Expand All @@ -156,36 +147,13 @@ class ImageManager {
return null;
}

const width = image.data.width + padding * 2;
const height = image.data.height + padding * 2;

const bin = this.shelfPack.packOne(width, height);
if (!bin) {
return null;
}

this.atlasImage.resize(this.getPixelSize());

const src = image.data;
const dst = this.atlasImage;

const x = bin.x + padding;
const y = bin.y + padding;
const w = src.width;
const h = src.height;

RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });

// Add 1 pixel wrapped padding on each side of the image.
RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x: x, y: y - 1 }, { width: w, height: 1 }); // T
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x, y: y + h }, { width: w, height: 1 }); // B
RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y: y }, { width: 1, height: h }); // L
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y: y }, { width: 1, height: h }); // R

this.dirty = true;

const w = image.data.width + padding * 2;
const h = image.data.height + padding * 2;
const bin = {w, h, x: 0, y: 0};
const position = new ImagePosition(bin, image);
this.patterns[id] = { bin, position };
this.patterns[id] = {bin, position};
this._updatePatternAtlas();

return position;
}

Expand All @@ -200,6 +168,37 @@ class ImageManager {

this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
}

_updatePatternAtlas() {
const bins = [];
for (const id in this.patterns) {
bins.push(this.patterns[id].bin);
}

const {w, h} = potpack(bins);

const dst = this.atlasImage;
dst.resize({width: w, height: h});

for (const id in this.patterns) {
const {bin} = this.patterns[id];
const x = bin.x + padding;
const y = bin.y + padding;
const src = this.images[id].data;
const w = src.width;
const h = src.height;

RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });

// Add 1 pixel wrapped padding on each side of the image.
RGBAImage.copy(src, dst, { x: 0, y: h - 1 }, { x: x, y: y - 1 }, { width: w, height: 1 }); // T
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x, y: y + h }, { width: w, height: 1 }); // B
RGBAImage.copy(src, dst, { x: w - 1, y: 0 }, { x: x - 1, y: y }, { width: 1, height: h }); // L
RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x: x + w, y: y }, { width: 1, height: h }); // R
}

this.dirty = true;
}
}

export default ImageManager;
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,6 @@
debounce "^1.0.2"
xtend "^4.0.1"

"@mapbox/shelf-pack@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@mapbox/shelf-pack/-/shelf-pack-3.2.0.tgz#df3630ecce8c042817c9a365b88078412963de64"

"@mapbox/sphericalmercator@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
Expand Down Expand Up @@ -7715,6 +7711,10 @@ postcss@^6.0.11, postcss@^6.0.21, postcss@^6.0.23:
source-map "^0.6.1"
supports-color "^5.4.0"

potpack@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf"

prebuild-install@^2.1.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.5.1.tgz#0f234140a73760813657c413cdccdda58296b1da"
Expand Down

0 comments on commit 8dd624d

Please sign in to comment.