Skip to content

Commit

Permalink
Eliminite the half-pixel correction that distorts imagery textures
Browse files Browse the repository at this point in the history
(closes #1650)

Followup from 3bd4723
Now all packed textures will include 1px of padding, and we'll duplicate each 1px
edge of pixels into the padding, so we should never sample colors from a neighboring
texture, and the image should never be distorted because of the half pixel uv correction.

The previous commit did a bunch of extra `texSubImage2D` to accomplish this,
now I'm just copying the raw pixels that we need, so seems pretty quick.

To make this easier, I'm changing the AtlasAllocator to only work with ImageData now
that we need access to pixels.  This means that any code that was trying to pack
something else needs to convert it to an ImageData first.

This commit also doubles up the resolution of the SVG symbols, since some of
them are used for Mapillary Signs, and these can have a lot of text/detail.
The Mapillary signs look noticeably better now.
Probably all the other SVG symbols got a slight visual improvement too.
  • Loading branch information
bhousel committed Jan 6, 2025
1 parent 3bd4723 commit d19702f
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 204 deletions.
2 changes: 1 addition & 1 deletion modules/core/GraphicsSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ export class GraphicsSystem extends AbstractSystem {
if (debug.texture) {
debug.texture.destroy();
}
debug.texture = this.textures.getDebugTexture('text');
debug.texture = this.textures.getDebugTexture('symbol');
debug.position.set(50, -200);
screen.position.set(50, -200);
}
Expand Down
9 changes: 5 additions & 4 deletions modules/pixi/PixiLayerMapillarySigns.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ export class PixiLayerMapillarySigns extends AbstractLayer {
feature.style = style;
feature.parentContainer = parentContainer;
feature.setData(d.id, d);
// const marker = feature.marker;
// const ICONSIZE = 24;
// marker.width = ICONSIZE;
// marker.height = ICONSIZE;

const marker = feature.marker;
const ICONSIZE = 24;
marker.width = ICONSIZE;
marker.height = ICONSIZE;
}

this.syncFeatureClasses(feature);
Expand Down
49 changes: 33 additions & 16 deletions modules/pixi/PixiTextures.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class PixiTextures {
/**
* allocate
* This packs an asset into one of the atlases and tracks it in the textureData map
* The asset can be one of: HTMLImageElement | HTMLCanvasElement | ImageBitmap | ImageData | ArrayBufferView
* The asset must be one of: ImageData | Uint8ClampedArray | HTMLCanvasElement | HTMLImageElement
* @param {string} atlasID One of 'symbol', 'text', or 'tile'
* @param {string} textureID e.g. 'boldPin', 'Main Street-normal', 'Bing-0,1,2'
* @param {number} width width in pixels
Expand All @@ -188,24 +188,40 @@ export class PixiTextures {
return tdata.texture;
}

const avoidSeams = (atlasID === 'tile');
const texture = atlas.allocate(width, height, asset, avoidSeams);
// To simplify the atlas code, get everything into an `ImageData` before packing
let imageData;
if (asset instanceof ImageData) {
imageData = asset;

} else if (asset instanceof Uint8ClampedArray) {
imageData = new ImageData(asset, width, height);

} else if (asset instanceof HTMLCanvasElement) {
// note that the canvas dimensions may be larger than the passed-in dimensions
const ctx = asset.getContext('2d');
imageData = ctx.getImageData(0, 0, width, height); // not the canvas width/height

} else if (asset instanceof HTMLImageElement) {
const [w, h] = [asset.naturalWidth, asset.naturalHeight];
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.drawImage(asset, 0, 0);
imageData = ctx.getImageData(0, 0, w, h);

} else {
return null; // some other format?
}


const texture = atlas.allocate(imageData);
if (!texture) {
throw new Error(`Couldn't allocate texture ${key}`);
}

texture.label = key;

// For tiles we want to preserve their power of 2 dimensions - so no padding!
// But we also want to prevent their colors from spilling into an adjacent tile in the atlas.
// Shrink texture coords by half pixel to avoid this.
// https://gamedev.stackexchange.com/a/49585
// if (atlasID === 'tile') {
// const rect = texture.frame.clone().pad(-0.5);
// texture.frame = rect; // `.frame` setter will call updateUvs() automatically
// texture.update(); // maybe not in pixi v8? I'm still seeing tile seams?
// }

this._textureData.set(key, { texture: texture, refcount: 1 });

return texture;
Expand Down Expand Up @@ -260,7 +276,7 @@ export class PixiTextures {

const renderer = this.gfx.pixi.renderer;
const temp = renderer.generateTexture(options);
const { pixels, width, height } = renderer.texture.getPixels(temp);
const { pixels, width, height } = renderer.texture.getPixels(temp); // a Uint8ClampedArray
const texture = this.allocate('symbol', textureID, width, height, pixels);

// These textures are overscaled, but `orig` Rectangle stores the original width/height
Expand Down Expand Up @@ -330,12 +346,13 @@ export class PixiTextures {
// see https://github.com/facebook/Rapid/commit/dd24e912

const viewBox = symbol.getAttribute('viewBox');
const size = 64; // somewhat large, but some mapillary signs have a lot of detail/text in them

// Make a new <svg> container
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
svg.setAttribute('width', '32');
svg.setAttribute('height', '32');
svg.setAttribute('width', size);
svg.setAttribute('height', size);
svg.setAttribute('color', '#fff'); // white so we can tint them
svg.setAttribute('viewBox', viewBox);

Expand Down
Loading

0 comments on commit d19702f

Please sign in to comment.