Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve image/glyph atlas packing algorithm #7171

Merged
merged 4 commits into from
Aug 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a regular dependency rather than a dev dependency?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, my misstep, although it actually doesn't affect anything because GL JS is only usable with the built bundle. We might want to move most other regular deps to devdeps then.

"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