Get pixel data

This commit is contained in:
Tim Schaub
2022-02-06 16:46:01 -07:00
parent cd45663996
commit eb4d5e0784
23 changed files with 721 additions and 101 deletions
+16
View File
@@ -47,6 +47,14 @@ class LayerRenderer extends Observable {
return abstract();
}
/**
* @param {import("../pixel.js").Pixel} pixel Pixel.
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView|null} Pixel data.
*/
getData(pixel) {
return null;
}
/**
* Determine whether render should be called.
* @abstract
@@ -191,6 +199,14 @@ class LayerRenderer extends Observable {
layer.changed();
}
}
/**
* Clean up.
*/
disposeInternal() {
delete this.layer_;
super.disposeInternal();
}
}
export default LayerRenderer;
+50 -1
View File
@@ -5,15 +5,19 @@ import CanvasLayerRenderer from './Layer.js';
import ViewHint from '../../ViewHint.js';
import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js';
import {IMAGE_SMOOTHING_DISABLED, IMAGE_SMOOTHING_ENABLED} from './common.js';
import {assign} from '../../obj.js';
import {
apply as applyTransform,
compose as composeTransform,
makeInverse,
toString as toTransformString,
} from '../../transform.js';
import {assign} from '../../obj.js';
import {
containsCoordinate,
containsExtent,
getHeight,
getIntersection,
getWidth,
intersects as intersectsExtent,
isEmpty,
} from '../../extent.js';
@@ -98,6 +102,51 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
return !!this.image_;
}
/**
* @param {import("../../pixel.js").Pixel} pixel Pixel.
* @return {Uint8ClampedArray} Data at the pixel location.
*/
getData(pixel) {
const frameState = this.frameState;
if (!frameState) {
return null;
}
const layer = this.getLayer();
const coordinate = applyTransform(
frameState.pixelToCoordinateTransform,
pixel.slice()
);
const layerExtent = layer.getExtent();
if (layerExtent) {
if (!containsCoordinate(layerExtent, coordinate)) {
return null;
}
}
const imageExtent = this.image_.getExtent();
const img = this.image_.getImage();
const imageMapWidth = getWidth(imageExtent);
const col = Math.floor(
img.width * ((coordinate[0] - imageExtent[0]) / imageMapWidth)
);
if (col < 0 || col >= img.width) {
return null;
}
const imageMapHeight = getHeight(imageExtent);
const row = Math.floor(
img.height * ((imageExtent[3] - coordinate[1]) / imageMapHeight)
);
if (row < 0 || row >= img.height) {
return null;
}
return this.getImageData(img, col, row);
}
/**
* Render the layer.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
+49
View File
@@ -20,6 +20,18 @@ import {
import {createCanvasContext2D} from '../../dom.js';
import {equals} from '../../array.js';
/**
* @type {CanvasRenderingContext2D}
*/
let pixelContext = null;
function createPixelContext() {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
pixelContext = canvas.getContext('2d');
}
/**
* @abstract
* @template {import("../../layer/Layer.js").default} LayerType
@@ -83,6 +95,34 @@ class CanvasLayerRenderer extends LayerRenderer {
* @type {CanvasRenderingContext2D}
*/
this.pixelContext_ = null;
/**
* @protected
* @type {import("../../PluggableMap.js").FrameState|null}
*/
this.frameState = null;
}
/**
* @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image.
* @param {number} col The column index.
* @param {number} row The row index.
* @return {Uint8ClampedArray|null} The image data.
*/
getImageData(image, col, row) {
if (!pixelContext) {
createPixelContext();
}
pixelContext.clearRect(0, 0, 1, 1);
let data;
try {
pixelContext.drawImage(image, col, row, 1, 1, 0, 0, 1, 1);
data = pixelContext.getImageData(0, 0, 1, 1).data;
} catch (err) {
return null;
}
return data;
}
/**
@@ -215,6 +255,7 @@ class CanvasLayerRenderer extends LayerRenderer {
* @protected
*/
preRender(context, frameState) {
this.frameState = frameState;
this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState);
}
@@ -324,6 +365,14 @@ class CanvasLayerRenderer extends LayerRenderer {
}
return data;
}
/**
* Clean up.
*/
disposeInternal() {
delete this.frameState;
super.disposeInternal();
}
}
export default CanvasLayerRenderer;
+77
View File
@@ -2,6 +2,8 @@
* @module ol/renderer/canvas/TileLayer
*/
import CanvasLayerRenderer from './Layer.js';
import ImageTile from '../../ImageTile.js';
import ReprojTile from '../../reproj/Tile.js';
import TileRange from '../../TileRange.js';
import TileState from '../../TileState.js';
import {IMAGE_SMOOTHING_DISABLED, IMAGE_SMOOTHING_ENABLED} from './common.js';
@@ -13,6 +15,7 @@ import {
} from '../../transform.js';
import {assign} from '../../obj.js';
import {
containsCoordinate,
createEmpty,
equals,
getIntersection,
@@ -22,6 +25,7 @@ import {cssOpacity} from '../../css.js';
import {fromUserExtent} from '../../proj.js';
import {getUid} from '../../util.js';
import {numberSafeCompareFunction} from '../../array.js';
import {toSize} from '../../size.js';
/**
* @classdesc
@@ -136,6 +140,79 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
return tile;
}
/**
* @param {import("../../pixel.js").Pixel} pixel Pixel.
* @return {Uint8ClampedArray} Data at the pixel location.
*/
getData(pixel) {
const frameState = this.frameState;
if (!frameState) {
return null;
}
const layer = this.getLayer();
const coordinate = applyTransform(
frameState.pixelToCoordinateTransform,
pixel.slice()
);
const layerExtent = layer.getExtent();
if (layerExtent) {
if (!containsCoordinate(layerExtent, coordinate)) {
return null;
}
}
const pixelRatio = frameState.pixelRatio;
const projection = frameState.viewState.projection;
const viewState = frameState.viewState;
const source = layer.getRenderSource();
const tileGrid = source.getTileGridForProjection(viewState.projection);
const tilePixelRatio = source.getTilePixelRatio(frameState.pixelRatio);
for (
let z = tileGrid.getZForResolution(viewState.resolution);
z >= tileGrid.getMinZoom();
--z
) {
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
const tile = source.getTile(
z,
tileCoord[1],
tileCoord[2],
pixelRatio,
projection
);
if (!(tile instanceof ImageTile || tile instanceof ReprojTile)) {
return null;
}
if (tile.getState() !== TileState.LOADED) {
continue;
}
const tileOrigin = tileGrid.getOrigin(z);
const tileSize = toSize(tileGrid.getTileSize(z));
const tileResolution = tileGrid.getResolution(z);
const col = Math.floor(
tilePixelRatio *
((coordinate[0] - tileOrigin[0]) / tileResolution -
tileCoord[1] * tileSize[0])
);
const row = Math.floor(
tilePixelRatio *
((tileOrigin[1] - coordinate[1]) / tileResolution -
tileCoord[2] * tileSize[1])
);
return this.getImageData(tile.getImage(), col, row);
}
return null;
}
/**
* @param {Object<number, Object<string, import("../../Tile.js").default>>} tiles Lookup of loaded tiles by zoom level.
* @param {number} zoom Zoom level.
+94 -8
View File
@@ -11,9 +11,11 @@ import WebGLLayerRenderer from './Layer.js';
import {AttributeType} from '../../webgl/Helper.js';
import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
import {
apply as applyTransform,
compose as composeTransform,
create as createTransform,
} from '../../transform.js';
import {containsCoordinate, getIntersection, isEmpty} from '../../extent.js';
import {
create as createMat4,
fromTransform as mat4FromTransform,
@@ -23,7 +25,6 @@ import {
getKey as getTileCoordKey,
} from '../../tilecoord.js';
import {fromUserExtent} from '../../proj.js';
import {getIntersection, isEmpty} from '../../extent.js';
import {getUid} from '../../util.js';
import {numberSafeCompareFunction} from '../../array.js';
import {toSize} from '../../size.js';
@@ -233,6 +234,12 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
* @private
*/
this.paletteTextures_ = options.paletteTextures || [];
/**
* @private
* @type {import("../../PluggableMap.js").FrameState|null}
*/
this.frameState_ = null;
}
/**
@@ -355,13 +362,13 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
viewState.projection
);
if (!tileTexture) {
tileTexture = new TileTexture(
tile,
tileGrid,
this.helper,
tilePixelRatio,
gutter
);
tileTexture = new TileTexture({
tile: tile,
grid: tileGrid,
helper: this.helper,
tilePixelRatio: tilePixelRatio,
gutter: gutter,
});
tileTextureCache.set(cacheKey, tileTexture);
} else {
if (this.isDrawableTile_(tile)) {
@@ -401,6 +408,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
* @return {HTMLElement} The rendered element.
*/
renderFrame(frameState) {
this.frameState_ = frameState;
this.renderComplete = true;
const gl = this.helper.getGL();
this.preRender(gl, frameState);
@@ -648,6 +656,83 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
return canvas;
}
/**
* @param {import("../../pixel.js").Pixel} pixel Pixel.
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView} Data at the pixel location.
*/
getData(pixel) {
const gl = this.helper.getGL();
if (!gl) {
return null;
}
const frameState = this.frameState_;
if (!frameState) {
return null;
}
const layer = this.getLayer();
const coordinate = applyTransform(
frameState.pixelToCoordinateTransform,
pixel.slice()
);
const viewState = frameState.viewState;
const layerExtent = layer.getExtent();
if (layerExtent) {
if (
!containsCoordinate(
fromUserExtent(layerExtent, viewState.projection),
coordinate
)
) {
return null;
}
}
const source = layer.getRenderSource();
const tileGrid = source.getTileGridForProjection(viewState.projection);
if (!source.getWrapX()) {
const gridExtent = tileGrid.getExtent();
if (gridExtent) {
if (!containsCoordinate(gridExtent, coordinate)) {
return null;
}
}
}
const tileTextureCache = this.tileTextureCache_;
for (
let z = tileGrid.getZForResolution(viewState.resolution);
z >= tileGrid.getMinZoom();
--z
) {
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
const cacheKey = getCacheKey(source, tileCoord);
if (!tileTextureCache.containsKey(cacheKey)) {
continue;
}
const tileTexture = tileTextureCache.get(cacheKey);
if (!tileTexture.loaded) {
continue;
}
const tileOrigin = tileGrid.getOrigin(z);
const tileSize = toSize(tileGrid.getTileSize(z));
const tileResolution = tileGrid.getResolution(z);
const col =
(coordinate[0] - tileOrigin[0]) / tileResolution -
tileCoord[1] * tileSize[0];
const row =
(tileOrigin[1] - coordinate[1]) / tileResolution -
tileCoord[2] * tileSize[1];
return tileTexture.getPixelData(col, row);
}
return null;
}
/**
* Look for tiles covering the provided tile coordinate at an alternate
* zoom level. Loaded tiles will be added to the provided tile texture lookup.
@@ -719,6 +804,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
delete this.indices_;
delete this.tileTextureCache_;
delete this.frameState_;
}
}