Leaflet.js(或其他解决方案)缩放到无模糊的放大像素
Leaflet.js (or other solution) zoom to magnified pixels without blur
我最近一直在使用 Leaflet 显示光栅图像。
我想为特定项目做的是能够放大图像,使像素以清晰划定的方式在屏幕上放大,例如在放大图像时会看到Photoshop之类的。我还想在最大之前的某个缩放级别保留图像像素和屏幕像素之间的 1:1 对应关系。
我尝试按照 here and here 所述超越 maxNativeZoom,这有效,但插值导致像素模糊。
我想到了一个替代方案,即使用 'nearest neighbour' 插值将每个像素扩展为更大的正方形,使源图像更大:当缩放到 maxNativeZoom 时,正方形看起来像急剧放大的像素,即使它们不是。
问题是:
- 图像大小和图块数量很快就会失控(原始图像为 4096 x 4096)
- 你永远不会得到图像像素和屏幕像素之间1:1对应关系的'pop'
我考虑过使用两个图块集:第一个是从原始图像到它的 maxNativeZoom,然后是更大的 'nearest neighbour' 插值图像,之后是 this.
但是,这更复杂,并没有避免大量图块的问题,而且显得不够优雅。
所以:
- Leaflet 可以做我需要的吗?如果可以,怎么做?
- 如果不能,你能给我指出正确的方向吗(例如,知道如何实现 this 会很有趣)?
非常感谢
一种方法是利用 image-rendering
CSS property。这可以提示浏览器在 <img>
元素上使用最近邻插值,例如 Leaflet 地图图块。
例如:
img.leaflet-tile {
image-rendering: pixelated;
}
看到一个working demo. Beware of incomplete browser support。
一种更复杂的方法(但适用于更多浏览器的方法)是利用 WebGL; in particular Leaflet.TileLayer.GL.
这涉及一些internal changes to Leaflet.TileLayer.GL to support a per-tile uniform,最关键的是在每个瓦片渲染中将统一值设置为瓦片坐标...
gl.uniform3f(this._uTileCoordsPosition, coords.x, coords.y, coords.z);
...有一个 L.TileLayer
"displays" 一个非过度缩放的图块用于过度缩放的图块坐标(而不是仅仅跳过不存在的图块)...
var hackishTilelayer = new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
maxNonPixelatedZoom: 3
});
hackishTilelayer.getTileUrl = function(coords) {
if (coords.z > this.options.maxNonPixelatedZoom) {
return this.getTileUrl({
x: Math.floor(coords.x / 2),
y: Math.floor(coords.y / 2),
z: coords.z - 1
});
}
// Skip L.TileLayer.prototype.getTileUrl.call(this, coords), instead
// apply the URL template directly to avoid maxNativeZoom shenanigans
var data = {
r: L.Browser.retina ? '@2x' : '',
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: coords.z // *not* this._getZoomForUrl() !
};
var url = L.Util.template(this._url, L.Util.extend(data, this.options));
return url;
}
...加上一个片段着色器,它在提取纹素之前向下舍入纹素坐标(加上一个平铺坐标模数相关偏移),以实际执行最近邻过采样...
var fragmentShader = `
highp float factor = max(1., pow(2., uTileCoords.z - uPixelatedZoomLevel));
vec2 subtileOffset = mod(uTileCoords.xy, factor);
void main(void) {
vec2 texelCoord = floor(vTextureCoords.st * uTileSize / factor ) / uTileSize;
texelCoord.xy += subtileOffset / factor;
vec4 texelColour = texture2D(uTexture0, texelCoord);
// This would output the image colours "as is"
gl_FragColor = texelColour;
}
`;
...在 L.TileLayer.GL
的一个实例中全部捆绑在一起(同步周围制服的一些数字):
var pixelated = L.tileLayer.gl({
fragmentShader: fragmentShader,
tileLayers: [hackishTilelayer],
uniforms: {
// The shader will need the zoom level as a uniform...
uPixelatedZoomLevel: hackishTilelayer.options.maxNonPixelatedZoom,
// ...as well as the tile size in pixels.
uTileSize: [hackishTilelayer.getTileSize().x, hackishTilelayer.getTileSize().y]
}
}).addTo(map);
您可以看到一切都在协同工作 in this demo。
我最近一直在使用 Leaflet 显示光栅图像。
我想为特定项目做的是能够放大图像,使像素以清晰划定的方式在屏幕上放大,例如在放大图像时会看到Photoshop之类的。我还想在最大之前的某个缩放级别保留图像像素和屏幕像素之间的 1:1 对应关系。
我尝试按照 here and here 所述超越 maxNativeZoom,这有效,但插值导致像素模糊。
我想到了一个替代方案,即使用 'nearest neighbour' 插值将每个像素扩展为更大的正方形,使源图像更大:当缩放到 maxNativeZoom 时,正方形看起来像急剧放大的像素,即使它们不是。
问题是:
- 图像大小和图块数量很快就会失控(原始图像为 4096 x 4096)
- 你永远不会得到图像像素和屏幕像素之间1:1对应关系的'pop'
我考虑过使用两个图块集:第一个是从原始图像到它的 maxNativeZoom,然后是更大的 'nearest neighbour' 插值图像,之后是 this.
但是,这更复杂,并没有避免大量图块的问题,而且显得不够优雅。
所以:
- Leaflet 可以做我需要的吗?如果可以,怎么做?
- 如果不能,你能给我指出正确的方向吗(例如,知道如何实现 this 会很有趣)?
非常感谢
一种方法是利用 image-rendering
CSS property。这可以提示浏览器在 <img>
元素上使用最近邻插值,例如 Leaflet 地图图块。
例如:
img.leaflet-tile {
image-rendering: pixelated;
}
看到一个working demo. Beware of incomplete browser support。
一种更复杂的方法(但适用于更多浏览器的方法)是利用 WebGL; in particular Leaflet.TileLayer.GL.
这涉及一些internal changes to Leaflet.TileLayer.GL to support a per-tile uniform,最关键的是在每个瓦片渲染中将统一值设置为瓦片坐标...
gl.uniform3f(this._uTileCoordsPosition, coords.x, coords.y, coords.z);
...有一个 L.TileLayer
"displays" 一个非过度缩放的图块用于过度缩放的图块坐标(而不是仅仅跳过不存在的图块)...
var hackishTilelayer = new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
maxNonPixelatedZoom: 3
});
hackishTilelayer.getTileUrl = function(coords) {
if (coords.z > this.options.maxNonPixelatedZoom) {
return this.getTileUrl({
x: Math.floor(coords.x / 2),
y: Math.floor(coords.y / 2),
z: coords.z - 1
});
}
// Skip L.TileLayer.prototype.getTileUrl.call(this, coords), instead
// apply the URL template directly to avoid maxNativeZoom shenanigans
var data = {
r: L.Browser.retina ? '@2x' : '',
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: coords.z // *not* this._getZoomForUrl() !
};
var url = L.Util.template(this._url, L.Util.extend(data, this.options));
return url;
}
...加上一个片段着色器,它在提取纹素之前向下舍入纹素坐标(加上一个平铺坐标模数相关偏移),以实际执行最近邻过采样...
var fragmentShader = `
highp float factor = max(1., pow(2., uTileCoords.z - uPixelatedZoomLevel));
vec2 subtileOffset = mod(uTileCoords.xy, factor);
void main(void) {
vec2 texelCoord = floor(vTextureCoords.st * uTileSize / factor ) / uTileSize;
texelCoord.xy += subtileOffset / factor;
vec4 texelColour = texture2D(uTexture0, texelCoord);
// This would output the image colours "as is"
gl_FragColor = texelColour;
}
`;
...在 L.TileLayer.GL
的一个实例中全部捆绑在一起(同步周围制服的一些数字):
var pixelated = L.tileLayer.gl({
fragmentShader: fragmentShader,
tileLayers: [hackishTilelayer],
uniforms: {
// The shader will need the zoom level as a uniform...
uPixelatedZoomLevel: hackishTilelayer.options.maxNonPixelatedZoom,
// ...as well as the tile size in pixels.
uTileSize: [hackishTilelayer.getTileSize().x, hackishTilelayer.getTileSize().y]
}
}).addTo(map);
您可以看到一切都在协同工作 in this demo。