Skip to content

Commit

Permalink
Allow setting a custom pixel ratio (maplibre#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
vanilla-lake committed Jan 13, 2022
1 parent 22ae714 commit 8e12709
Show file tree
Hide file tree
Showing 22 changed files with 141 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- Improve attribution controls accessibility. See [#359](https://github.com/maplibre/maplibre-gl-js/issues/359)
- Allow maxPitch value up to 85, use values greater than 60 at your own risk (#574)
- `getImage` uses createImageBitmap when supported (#650)
- Allow setting a custom pixel ratio by adding a `MapOptions#pixelRatio` property and a `Map#setPixelRatio` method (#769)
- *...Add new stuff here...*

### 🐞 Bug fixes
Expand Down
6 changes: 5 additions & 1 deletion bench/lib/tile_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class StubMap extends Evented {
super();
this._requestManager = new RequestManager();
}

getPixelRatio() {
return devicePixelRatio;
}
}

const mapStub = new StubMap() as any as Map;
Expand Down Expand Up @@ -104,7 +108,7 @@ export default class TileParser {
}

fetchTile(tileID: OverscaledTileID) {
return fetch(tileID.canonical.url(this.tileJSON.tiles))
return fetch(tileID.canonical.url(this.tileJSON.tiles, devicePixelRatio))
.then(response => response.arrayBuffer())
.then(buffer => ({tileID, buffer}));
}
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function drawDebugSSRect(painter: Painter, x: number, y: number, width: number,
const gl = context.gl;

gl.enable(gl.SCISSOR_TEST);
gl.scissor(x * devicePixelRatio, y * devicePixelRatio, width * devicePixelRatio, height * devicePixelRatio);
gl.scissor(x * painter.pixelRatio, y * painter.pixelRatio, width * painter.pixelRatio, height * painter.pixelRatio);
context.clear({color});
gl.disable(gl.SCISSOR_TEST);
}
Expand Down
8 changes: 5 additions & 3 deletions src/render/painter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class Painter {
emptyProgramConfiguration: ProgramConfiguration;
width: number;
height: number;
pixelRatio: number;
tileExtentBuffer: VertexBuffer;
tileExtentSegments: SegmentVector;
debugBuffer: VertexBuffer;
Expand Down Expand Up @@ -146,9 +147,10 @@ class Painter {
* Update the GL viewport, projection matrix, and transforms to compensate
* for a new width and height value.
*/
resize(width: number, height: number) {
this.width = width * devicePixelRatio;
this.height = height * devicePixelRatio;
resize(width: number, height: number, pixelRatio: number) {
this.width = width * pixelRatio;
this.height = height * pixelRatio;
this.pixelRatio = pixelRatio;
this.context.viewport.set([0, 0, this.width, this.height]);

if (this.style) {
Expand Down
2 changes: 1 addition & 1 deletion src/render/program/circle_program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const circleUniformValues = (
layer.paint.get('circle-translate'),
layer.paint.get('circle-translate-anchor')),
'u_pitch_with_map': +(pitchWithMap),
'u_device_pixel_ratio': devicePixelRatio,
'u_device_pixel_ratio': painter.pixelRatio,
'u_extrude_scale': extrudeScale
};
};
Expand Down
6 changes: 3 additions & 3 deletions src/render/program/line_program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const lineUniformValues = (painter: Painter, tile: Tile, layer: LineStyleLayer):
return {
'u_matrix': calculateMatrix(painter, tile, layer),
'u_ratio': 1 / pixelsToTileUnits(tile, 1, transform.zoom),
'u_device_pixel_ratio': devicePixelRatio,
'u_device_pixel_ratio': painter.pixelRatio,
'u_units_to_pixels': [
1 / transform.pixelsToGLUnits[0],
1 / transform.pixelsToGLUnits[1]
Expand Down Expand Up @@ -127,7 +127,7 @@ const linePatternUniformValues = (
'u_texsize': tile.imageAtlasTexture.size,
// camera zoom ratio
'u_ratio': 1 / pixelsToTileUnits(tile, 1, transform.zoom),
'u_device_pixel_ratio': devicePixelRatio,
'u_device_pixel_ratio': painter.pixelRatio,
'u_image': 0,
'u_scale': [tileZoomRatio, crossfade.fromScale, crossfade.toScale],
'u_fade': crossfade.t,
Expand Down Expand Up @@ -160,7 +160,7 @@ const lineSDFUniformValues = (
return extend(lineUniformValues(painter, tile, layer), {
'u_patternscale_a': [tileRatio / widthA, -posA.height / 2],
'u_patternscale_b': [tileRatio / widthB, -posB.height / 2],
'u_sdfgamma': lineAtlas.width / (Math.min(widthA, widthB) * 256 * devicePixelRatio) / 2,
'u_sdfgamma': lineAtlas.width / (Math.min(widthA, widthB) * 256 * painter.pixelRatio) / 2,
'u_image': 0,
'u_tex_y_a': posA.y,
'u_tex_y_b': posB.y,
Expand Down
2 changes: 1 addition & 1 deletion src/render/program/symbol_program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ const symbolSDFUniformValues = (
rotateInShader, pitchWithMap, painter, matrix, labelPlaneMatrix,
glCoordMatrix, isText, texSize), {
'u_gamma_scale': (pitchWithMap ? Math.cos(transform._pitch) * transform.cameraToCenterDistance : 1),
'u_device_pixel_ratio': devicePixelRatio,
'u_device_pixel_ratio': painter.pixelRatio,
'u_is_halo': +isHalo
});
};
Expand Down
3 changes: 2 additions & 1 deletion src/source/geojson_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ describe('GeoJSONSource#update', () => {

const source = new GeoJSONSource('id', {data: {}} as GeoJSONSourceOptions, mockDispatcher, undefined);
source.map = {
transform: {} as Transform
transform: {} as Transform,
getPixelRatio() { return 1; }
} as any;

source.on('data', (e) => {
Expand Down
2 changes: 1 addition & 1 deletion src/source/geojson_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ class GeoJSONSource extends Evented implements Source {
maxZoom: this.maxzoom,
tileSize: this.tileSize,
source: this.id,
pixelRatio: devicePixelRatio,
pixelRatio: this.map.getPixelRatio(),
showCollisionBoxes: this.map.showCollisionBoxes,
promoteId: this.promoteId
};
Expand Down
3 changes: 2 additions & 1 deletion src/source/raster_dem_tile_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ function createSource(options, transformCallback?) {
source.onAdd({
transform: {angle: 0, pitch: 0, showCollisionBoxes: false},
_getMapId: () => 1,
_requestManager: new RequestManager(transformCallback)
_requestManager: new RequestManager(transformCallback),
getPixelRatio() { return 1; }
} as any);

source.on('error', (e) => {
Expand Down
2 changes: 1 addition & 1 deletion src/source/raster_dem_tile_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class RasterDEMTileSource extends RasterTileSource implements Source {
}

loadTile(tile: Tile, callback: Callback<void>) {
const url = tile.tileID.canonical.url(this.tiles, this.scheme);
const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
tile.request = getImage(this.map._requestManager.transformRequest(url, ResourceType.Tile), imageLoaded.bind(this));

tile.neighboringTiles = this._getNeighboringTiles(tile.tileID);
Expand Down
3 changes: 2 additions & 1 deletion src/source/raster_tile_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ function createSource(options, transformCallback?) {
source.onAdd({
transform: {angle: 0, pitch: 0, showCollisionBoxes: false},
_getMapId: () => 1,
_requestManager: new RequestManager(transformCallback)
_requestManager: new RequestManager(transformCallback),
getPixelRatio() { return 1; }
} as any);

source.on('error', (e) => {
Expand Down
2 changes: 1 addition & 1 deletion src/source/raster_tile_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class RasterTileSource extends Evented implements Source {
}

loadTile(tile: Tile, callback: Callback<void>) {
const url = tile.tileID.canonical.url(this.tiles, this.scheme);
const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
tile.request = getImage(this.map._requestManager.transformRequest(url, ResourceType.Tile), (err, img) => {
delete tile.request;

Expand Down
22 changes: 10 additions & 12 deletions src/source/tile_id.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,33 @@ describe('CanonicalTileID', () => {
});

test('.url replaces {z}/{x}/{y}', () => {
expect(new CanonicalTileID(2, 1, 0).url(['{z}/{x}/{y}.json'])).toBe('2/1/0.json');
expect(new CanonicalTileID(2, 1, 0).url(['{z}/{x}/{y}.json'], 1)).toBe('2/1/0.json');
});

test('.url replaces {quadkey}', () => {
expect(new CanonicalTileID(1, 0, 0).url(['quadkey={quadkey}'])).toBe('quadkey=0');
expect(new CanonicalTileID(2, 0, 0).url(['quadkey={quadkey}'])).toBe('quadkey=00');
expect(new CanonicalTileID(2, 1, 1).url(['quadkey={quadkey}'])).toBe('quadkey=03');
expect(new CanonicalTileID(17, 22914, 52870).url(['quadkey={quadkey}'])).toBe('quadkey=02301322130000230');
expect(new CanonicalTileID(1, 0, 0).url(['quadkey={quadkey}'], 1)).toBe('quadkey=0');
expect(new CanonicalTileID(2, 0, 0).url(['quadkey={quadkey}'], 1)).toBe('quadkey=00');
expect(new CanonicalTileID(2, 1, 1).url(['quadkey={quadkey}'], 1)).toBe('quadkey=03');
expect(new CanonicalTileID(17, 22914, 52870).url(['quadkey={quadkey}'], 1)).toBe('quadkey=02301322130000230');

// Test case confirmed by quadkeytools package
// https://bitbucket.org/steele/quadkeytools/rollup/build/tsc/src/master/test/quadkey.js?fileviewer=file-view-default#quadkey.js-57
expect(new CanonicalTileID(6, 29, 3).url(['quadkey={quadkey}'])).toBe('quadkey=011123');
expect(new CanonicalTileID(6, 29, 3).url(['quadkey={quadkey}'], 1)).toBe('quadkey=011123');

});

test('.url replaces {bbox-epsg-3857}', () => {
expect(new CanonicalTileID(1, 0, 0).url(['bbox={bbox-epsg-3857}'])).toBe('bbox=-20037508.342789244,0,0,20037508.342789244');
expect(new CanonicalTileID(1, 0, 0).url(['bbox={bbox-epsg-3857}'], 1)).toBe('bbox=-20037508.342789244,0,0,20037508.342789244');
});

test('.url replaces {ratio}', () => {
devicePixelRatio = 2;
expect(new CanonicalTileID(1, 0, 0).url(['r={ratio}'])).toBe('r=@2x');
devicePixelRatio = 1;
expect(new CanonicalTileID(1, 0, 0).url(['r={ratio}'])).toBe('r=');
expect(new CanonicalTileID(1, 0, 0).url(['r={ratio}'], 2)).toBe('r=@2x');
expect(new CanonicalTileID(1, 0, 0).url(['r={ratio}'], 1)).toBe('r=');
});

//Tests that multiple values of the same placeholder are replaced.
test('.url replaces {z}/{x}/{y}/{z}/{x}/{y}', () => {
expect(new CanonicalTileID(2, 1, 0).url(['{z}/{x}/{y}/{z}/{x}/{y}.json'])).toBe('2/1/0/2/1/0.json');
expect(new CanonicalTileID(2, 1, 0).url(['{z}/{x}/{y}/{z}/{x}/{y}.json'], 1)).toBe('2/1/0/2/1/0.json');
});

});
Expand Down
4 changes: 2 additions & 2 deletions src/source/tile_id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class CanonicalTileID {
}

// given a list of urls, choose a url template and return a tile URL
url(urls: Array<string>, scheme?: string | null) {
url(urls: Array<string>, pixelRatio: number, scheme?: string | null) {
const bbox = getTileBBox(this.x, this.y, this.z);
const quadkey = getQuadkey(this.z, this.x, this.y);

Expand All @@ -37,7 +37,7 @@ export class CanonicalTileID {
.replace(/{z}/g, String(this.z))
.replace(/{x}/g, String(this.x))
.replace(/{y}/g, String(scheme === 'tms' ? (Math.pow(2, this.z) - this.y - 1) : this.y))
.replace(/{ratio}/g, devicePixelRatio > 1 ? '@2x' : '')
.replace(/{ratio}/g, pixelRatio > 1 ? '@2x' : '')
.replace(/{quadkey}/g, quadkey)
.replace(/{bbox-epsg-3857}/g, bbox);
}
Expand Down
3 changes: 2 additions & 1 deletion src/source/vector_tile_source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ function createSource(options, transformCallback?) {
transform: {showCollisionBoxes: false},
_getMapId: () => 1,
_requestManager: new RequestManager(transformCallback),
style: {sourceCaches: {id: {clearTiles: () => {}}}}
style: {sourceCaches: {id: {clearTiles: () => {}}}},
getPixelRatio() { return 1; }
} as any as Map);

source.on('error', (e) => {
Expand Down
4 changes: 2 additions & 2 deletions src/source/vector_tile_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class VectorTileSource extends Evented implements Source {
}

loadTile(tile: Tile, callback: Callback<void>) {
const url = tile.tileID.canonical.url(this.tiles, this.scheme);
const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
const params = {
request: this.map._requestManager.transformRequest(url, ResourceType.Tile),
uid: tile.uid,
Expand All @@ -187,7 +187,7 @@ class VectorTileSource extends Evented implements Source {
tileSize: this.tileSize * tile.tileID.overscaleFactor(),
type: this.type,
source: this.id,
pixelRatio: devicePixelRatio,
pixelRatio: this.map.getPixelRatio(),
showCollisionBoxes: this.map.showCollisionBoxes,
promoteId: this.promoteId
};
Expand Down
3 changes: 2 additions & 1 deletion src/style/load_sprite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import type {Cancelable} from '../types/cancelable';
export default function(
baseURL: string,
requestManager: RequestManager,
pixelRatio: number,
callback: Callback<{[_: string]: StyleImage}>
): Cancelable {
let json: any, image, error;
const format = devicePixelRatio > 1 ? '@2x' : '';
const format = pixelRatio > 1 ? '@2x' : '';

let jsonRequest = getJSON(requestManager.transformRequest(requestManager.normalizeSpriteURL(baseURL, format, '.json'), ResourceType.SpriteJSON), (err?: Error | null, data?: any | null) => {
jsonRequest = null;
Expand Down
4 changes: 4 additions & 0 deletions src/style/style.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ class StubMap extends Evented {
_getMapId() {
return 1;
}

getPixelRatio() {
return 1;
}
}

const getStubMap = () => new StubMap() as any;
Expand Down
2 changes: 1 addition & 1 deletion src/style/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ class Style extends Evented {
}

_loadSprite(url: string) {
this._spriteRequest = loadSprite(url, this.map._requestManager, (err, images) => {
this._spriteRequest = loadSprite(url, this.map._requestManager, this.map.getPixelRatio(), (err, images) => {
this._spriteRequest = null;
if (err) {
this.fire(new ErrorEvent(err));
Expand Down
61 changes: 61 additions & 0 deletions src/ui/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2048,6 +2048,67 @@ describe('Map', () => {
});
});

describe('setPixelRatio', () => {
test('resizes canvas', () => {
const container = window.document.createElement('div');
Object.defineProperty(container, 'clientWidth', {value: 512});
Object.defineProperty(container, 'clientHeight', {value: 512});
const map = createMap({container, pixelRatio: 1});
expect(map.getCanvas().width).toBe(512);
expect(map.getCanvas().height).toBe(512);
map.setPixelRatio(2);
expect(map.getCanvas().width).toBe(1024);
expect(map.getCanvas().height).toBe(1024);
});

test('resizes painter', () => {
const container = window.document.createElement('div');
Object.defineProperty(container, 'clientWidth', {value: 512});
Object.defineProperty(container, 'clientHeight', {value: 512});
const map = createMap({container, pixelRatio: 1});
expect(map.painter.pixelRatio).toBe(1);
expect(map.painter.width).toBe(512);
expect(map.painter.height).toBe(512);
map.setPixelRatio(2);
expect(map.painter.pixelRatio).toBe(2);
expect(map.painter.width).toBe(1024);
expect(map.painter.height).toBe(1024);
});
});

describe('getPixelRatio', () => {
test('returns the pixel ratio', () => {
const map = createMap({pixelRatio: 1});
expect(map.getPixelRatio()).toBe(1);
map.setPixelRatio(2);
expect(map.getPixelRatio()).toBe(2);
});
});

test('pixel ratio defaults to devicePixelRatio', () => {
const map = createMap();
expect(map.getPixelRatio()).toBe(devicePixelRatio);
});

test('canvas has the expected size', () => {
const container = window.document.createElement('div');
Object.defineProperty(container, 'clientWidth', {value: 512});
Object.defineProperty(container, 'clientHeight', {value: 512});
const map = createMap({container, pixelRatio: 2});
expect(map.getCanvas().width).toBe(1024);
expect(map.getCanvas().height).toBe(1024);
});

test('painter has the expected size and pixel ratio', () => {
const container = window.document.createElement('div');
Object.defineProperty(container, 'clientWidth', {value: 512});
Object.defineProperty(container, 'clientHeight', {value: 512});
const map = createMap({container, pixelRatio: 2});
expect(map.painter.pixelRatio).toBe(2);
expect(map.painter.width).toBe(1024);
expect(map.painter.height).toBe(1024);
});

});

function createStyle() {
Expand Down
Loading

0 comments on commit 8e12709

Please sign in to comment.