package.webgl.TileTexture.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/webgl/TileTexture
*/
import BaseTileRepresentation from './BaseTileRepresentation.js';
import DataTile, {asArrayLike, asImageLike} from '../DataTile.js';
import ImageTile from '../ImageTile.js';
import ReprojTile from '../reproj/Tile.js';
import WebGLArrayBuffer from './Buffer.js';
import {ARRAY_BUFFER, STATIC_DRAW} from '../webgl.js';
import {createCanvasContext2D} from '../dom.js';
import {toSize} from '../size.js';
/**
* @param {WebGLRenderingContext} gl The WebGL context.
* @param {WebGLTexture} texture The texture.
* @param {boolean} interpolate Interpolate when resampling.
*/
function bindAndConfigure(gl, texture, interpolate) {
const resampleFilter = interpolate ? gl.LINEAR : gl.NEAREST;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, resampleFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, resampleFilter);
}
/**
* @param {WebGLRenderingContext} gl The WebGL context.
* @param {WebGLTexture} texture The texture.
* @param {import("../DataTile.js").ImageLike} image The image.
* @param {boolean} interpolate Interpolate when resampling.
*/
function uploadImageTexture(gl, texture, image, interpolate) {
bindAndConfigure(gl, texture, interpolate);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
}
/**
* @param {import("./Helper.js").default} helper The WebGL helper.
* @param {WebGLTexture} texture The texture.
* @param {import("../DataTile.js").ArrayLike} data The pixel data.
* @param {import("../size.js").Size} size The pixel size.
* @param {number} bandCount The band count.
* @param {boolean} interpolate Interpolate when resampling.
*/
function uploadDataTexture(
helper,
texture,
data,
size,
bandCount,
interpolate,
) {
const gl = helper.getGL();
let textureType;
let canInterpolate;
if (data instanceof Float32Array) {
textureType = gl.FLOAT;
helper.getExtension('OES_texture_float');
const extension = helper.getExtension('OES_texture_float_linear');
canInterpolate = extension !== null;
} else {
textureType = gl.UNSIGNED_BYTE;
canInterpolate = true;
}
bindAndConfigure(gl, texture, interpolate && canInterpolate);
const bytesPerRow = data.byteLength / size[1];
let unpackAlignment = 1;
if (bytesPerRow % 8 === 0) {
unpackAlignment = 8;
} else if (bytesPerRow % 4 === 0) {
unpackAlignment = 4;
} else if (bytesPerRow % 2 === 0) {
unpackAlignment = 2;
}
let format;
switch (bandCount) {
case 1: {
format = gl.LUMINANCE;
break;
}
case 2: {
format = gl.LUMINANCE_ALPHA;
break;
}
case 3: {
format = gl.RGB;
break;
}
case 4: {
format = gl.RGBA;
break;
}
default: {
throw new Error(`Unsupported number of bands: ${bandCount}`);
}
}
const oldUnpackAlignment = gl.getParameter(gl.UNPACK_ALIGNMENT);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
gl.texImage2D(
gl.TEXTURE_2D,
0,
format,
size[0],
size[1],
0,
format,
textureType,
data,
);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, oldUnpackAlignment);
}
/**
* @type {CanvasRenderingContext2D}
*/
let pixelContext = null;
function createPixelContext() {
pixelContext = createCanvasContext2D(1, 1, undefined, {
willReadFrequently: true,
});
}
/**
* @typedef {import("../DataTile.js").default|ImageTile|ReprojTile} TileType
*/
/**
* @extends {BaseTileRepresentation}
*/
class TileTexture extends BaseTileRepresentation {
/**
* @param {import("./BaseTileRepresentation.js").TileRepresentationOptions} options The tile texture options.
*/
constructor(options) {
super(options);
/**
* @type {Array}
*/
this.textures = [];
/**
* @type {import("../size.js").Size}
* @private
*/
this.renderSize_ = toSize(
options.grid.getTileSize(options.tile.tileCoord[0]),
);
/**
* @type {number}
*/
this.bandCount = NaN;
const coords = new WebGLArrayBuffer(ARRAY_BUFFER, STATIC_DRAW);
coords.fromArray([
0, // P0
1,
1, // P1
1,
1, // P2
0,
0, // P3
0,
]);
this.helper.flushBufferData(coords);
/**
* @type {WebGLArrayBuffer}
*/
this.coords = coords;
this.setTile(options.tile);
}
/**
* @override
* @param {import("./Helper.js").default} helper The WebGL helper.
*/
setHelper(helper) {
const gl = this.helper?.getGL();
if (gl) {
this.helper.deleteBuffer(this.coords);
for (let i = 0; i < this.textures.length; ++i) {
gl.deleteTexture(this.textures[i]);
}
}
super.setHelper(helper);
if (helper) {
helper.flushBufferData(this.coords);
}
}
/**
* @override
*/
uploadTile() {
const helper = this.helper;
const gl = helper.getGL();
const tile = this.tile;
this.textures.length = 0;
/**
* @type {import("../DataTile.js").Data}
*/
let data;
if (tile instanceof ImageTile || tile instanceof ReprojTile) {
data = tile.getImage();
} else {
data = tile.getData();
}
const image = asImageLike(data);
if (image) {
const texture = gl.createTexture();
this.textures.push(texture);
this.bandCount = 4;
uploadImageTexture(gl, texture, image, tile.interpolate);
this.setReady();
return;
}
data = asArrayLike(data);
const sourceTileSize = /** @type {DataTile} */ (tile).getSize();
const pixelSize = [
sourceTileSize[0] + 2 * this.gutter,
sourceTileSize[1] + 2 * this.gutter,
];
const isFloat = data instanceof Float32Array;
const pixelCount = pixelSize[0] * pixelSize[1];
const DataType = isFloat ? Float32Array : Uint8Array;
const bytesPerElement = DataType.BYTES_PER_ELEMENT;
const bytesPerRow = data.byteLength / pixelSize[1];
this.bandCount = Math.floor(bytesPerRow / bytesPerElement / pixelSize[0]);
const textureCount = Math.ceil(this.bandCount / 4);
if (textureCount === 1) {
const texture = gl.createTexture();
this.textures.push(texture);
uploadDataTexture(
helper,
texture,
data,
pixelSize,
this.bandCount,
tile.interpolate,
);
this.setReady();
return;
}
const textureDataArrays = new Array(textureCount);
for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
const texture = gl.createTexture();
this.textures.push(texture);
const bandCount =
textureIndex < textureCount - 1 ? 4 : ((this.bandCount - 1) % 4) + 1;
textureDataArrays[textureIndex] = new DataType(pixelCount * bandCount);
}
let dataIndex = 0;
let rowOffset = 0;
const colCount = pixelSize[0] * this.bandCount;
for (let rowIndex = 0; rowIndex < pixelSize[1]; ++rowIndex) {
for (let colIndex = 0; colIndex < colCount; ++colIndex) {
const dataValue = data[rowOffset + colIndex];
const pixelIndex = Math.floor(dataIndex / this.bandCount);
const bandIndex = colIndex % this.bandCount;
const textureIndex = Math.floor(bandIndex / 4);
const textureData = textureDataArrays[textureIndex];
const bandCount = textureData.length / pixelCount;
const textureBandIndex = bandIndex % 4;
textureData[pixelIndex * bandCount + textureBandIndex] = dataValue;
++dataIndex;
}
rowOffset += bytesPerRow / bytesPerElement;
}
for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
const texture = this.textures[textureIndex];
const textureData = textureDataArrays[textureIndex];
const bandCount = textureData.length / pixelCount;
uploadDataTexture(
helper,
texture,
textureData,
pixelSize,
bandCount,
tile.interpolate,
);
}
this.setReady();
}
/**
* @param {import("../DataTile.js").ImageLike} image The image.
* @param {number} renderCol The column index (in rendered tile space).
* @param {number} renderRow The row index (in rendered tile space).
* @return {Uint8ClampedArray|null} The data.
* @private
*/
getImagePixelData_(image, renderCol, renderRow) {
const gutter = this.gutter;
const renderWidth = this.renderSize_[0];
const renderHeight = this.renderSize_[1];
if (!pixelContext) {
createPixelContext();
}
pixelContext.clearRect(0, 0, 1, 1);
const sourceWidth = image.width;
const sourceHeight = image.height;
const sourceWidthWithoutGutter = sourceWidth - 2 * gutter;
const sourceHeightWithoutGutter = sourceHeight - 2 * gutter;
const sourceCol =
gutter + Math.floor(sourceWidthWithoutGutter * (renderCol / renderWidth));
const sourceRow =
gutter +
Math.floor(sourceHeightWithoutGutter * (renderRow / renderHeight));
let data;
try {
pixelContext.drawImage(image, sourceCol, sourceRow, 1, 1, 0, 0, 1, 1);
data = pixelContext.getImageData(0, 0, 1, 1).data;
} catch (err) {
pixelContext = null;
return null;
}
return data;
}
/**
* @param {import("../DataTile.js").ArrayLike} data The data.
* @param {import("../size.js").Size} sourceSize The size.
* @param {number} renderCol The column index (in rendered tile space).
* @param {number} renderRow The row index (in rendered tile space).
* @return {import("../DataTile.js").ArrayLike|null} The data.
* @private
*/
getArrayPixelData_(data, sourceSize, renderCol, renderRow) {
const gutter = this.gutter;
const renderWidth = this.renderSize_[0];
const renderHeight = this.renderSize_[1];
const sourceWidthWithoutGutter = sourceSize[0];
const sourceHeightWithoutGutter = sourceSize[1];
const sourceWidth = sourceWidthWithoutGutter + 2 * gutter;
const sourceHeight = sourceHeightWithoutGutter + 2 * gutter;
const sourceCol =
gutter + Math.floor(sourceWidthWithoutGutter * (renderCol / renderWidth));
const sourceRow =
gutter +
Math.floor(sourceHeightWithoutGutter * (renderRow / renderHeight));
if (data instanceof DataView) {
const bytesPerPixel = data.byteLength / (sourceWidth * sourceHeight);
const offset = bytesPerPixel * (sourceRow * sourceWidth + sourceCol);
const buffer = data.buffer.slice(offset, offset + bytesPerPixel);
return new DataView(buffer);
}
const offset = this.bandCount * (sourceRow * sourceWidth + sourceCol);
return data.slice(offset, offset + this.bandCount);
}
/**
* Get data for a pixel. If the tile is not loaded, null is returned.
* @param {number} renderCol The column index (in rendered tile space).
* @param {number} renderRow The row index (in rendered tile space).
* @return {import("../DataTile.js").ArrayLike|null} The data.
*/
getPixelData(renderCol, renderRow) {
if (!this.loaded) {
return null;
}
if (this.tile instanceof DataTile) {
const data = this.tile.getData();
const arrayData = asArrayLike(data);
if (arrayData) {
const sourceSize = this.tile.getSize();
return this.getArrayPixelData_(
arrayData,
sourceSize,
renderCol,
renderRow,
);
}
return this.getImagePixelData_(asImageLike(data), renderCol, renderRow);
}
return this.getImagePixelData_(this.tile.getImage(), renderCol, renderRow);
}
}
export default TileTexture;