Skip to content

Commit 3468ebd

Browse files
committed
feat: Support tiled pixelmaps with the canvas renderer
Previously, we supported pixelmap features on canvas and webgl, but only tiled pixelmap layers on webgl.
1 parent f9efc51 commit 3468ebd

File tree

5 files changed

+257
-71
lines changed

5 files changed

+257
-71
lines changed

src/canvas/pixelmapFeature.js

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ var canvas_pixelmapFeature = function (arg) {
4444
object.call(this);
4545

4646
var m_quadFeature,
47+
m_quadFeatureInit,
4748
s_exit = this._exit,
4849
m_this = this;
4950

@@ -59,21 +60,25 @@ var canvas_pixelmapFeature = function (arg) {
5960
* feature indices that are located at the specified point.
6061
*/
6162
this.pointSearch = function (geo, gcs) {
62-
if (m_quadFeature && m_this.m_info) {
63+
if (m_quadFeature) {
6364
var result = m_quadFeature.pointSearch(geo, gcs);
64-
if (result.index.length === 1 && result.extra && result.extra[result.index[0]].basis) {
65-
var basis = result.extra[result.index[0]].basis, x, y, idx;
66-
x = Math.floor(basis.x * m_this.m_info.width);
67-
y = Math.floor(basis.y * m_this.m_info.height);
68-
if (x >= 0 && x < m_this.m_info.width &&
65+
if (m_this.m_info) {
66+
if (result.index.length === 1 && result.extra && result.extra[result.index[0]].basis) {
67+
var basis = result.extra[result.index[0]].basis, x, y, idx;
68+
x = Math.floor(basis.x * m_this.m_info.width);
69+
y = Math.floor(basis.y * m_this.m_info.height);
70+
if (x >= 0 && x < m_this.m_info.width &&
6971
y >= 0 && y < m_this.m_info.height) {
70-
idx = m_this.m_info.indices[y * m_this.m_info.width + x];
71-
result = {
72-
index: [idx],
73-
found: [m_this.data()[idx]]
74-
};
75-
return result;
72+
idx = m_this.m_info.indices[y * m_this.m_info.width + x];
73+
result = {
74+
index: [idx],
75+
found: [m_this.data()[idx]]
76+
};
77+
return result;
78+
}
7679
}
80+
} else {
81+
return this._pointSearchProcess(result);
7782
}
7883
}
7984
return {index: [], found: []};
@@ -84,41 +89,47 @@ var canvas_pixelmapFeature = function (arg) {
8489
* if the pixelmap has already been prepared (it is invalidated by a change
8590
* in the image).
8691
*
92+
* @param {object} [quad] A quad to use as the base instead of the class
93+
* instance.
8794
* @returns {geo.pixelmapFeature.info?}
8895
*/
89-
this._preparePixelmap = function () {
96+
this._preparePixelmap = function (quad) {
97+
const base = quad || m_this;
98+
if (quad && quad.m_info) {
99+
return quad.m_info;
100+
}
90101
var i, idx, pixelData;
91102

92-
if (!util.isReadyImage(m_this.m_srcImage)) {
103+
if (!util.isReadyImage(base.m_srcImage)) {
93104
return undefined;
94105
}
95-
m_this.m_info = {
96-
width: m_this.m_srcImage.naturalWidth,
97-
height: m_this.m_srcImage.naturalHeight,
106+
base.m_info = {
107+
width: base.m_srcImage.naturalWidth,
108+
height: base.m_srcImage.naturalHeight,
98109
canvas: document.createElement('canvas')
99110
};
100111

101-
m_this.m_info.canvas.width = m_this.m_info.width;
102-
m_this.m_info.canvas.height = m_this.m_info.height;
103-
m_this.m_info.context = m_this.m_info.canvas.getContext('2d');
112+
base.m_info.canvas.width = base.m_info.width;
113+
base.m_info.canvas.height = base.m_info.height;
114+
base.m_info.context = base.m_info.canvas.getContext('2d');
104115

105-
m_this.m_info.context.drawImage(m_this.m_srcImage, 0, 0);
106-
m_this.m_info.imageData = m_this.m_info.context.getImageData(
107-
0, 0, m_this.m_info.canvas.width, m_this.m_info.canvas.height);
108-
pixelData = m_this.m_info.imageData.data;
109-
m_this.m_info.indices = new Array(pixelData.length / 4);
110-
m_this.m_info.area = pixelData.length / 4;
116+
base.m_info.context.drawImage(base.m_srcImage, 0, 0);
117+
base.m_info.imageData = base.m_info.context.getImageData(
118+
0, 0, base.m_info.canvas.width, base.m_info.canvas.height);
119+
pixelData = base.m_info.imageData.data;
120+
base.m_info.indices = new Array(pixelData.length / 4);
121+
base.m_info.area = pixelData.length / 4;
111122

112-
m_this.m_info.mappedColors = {};
123+
base.m_info.mappedColors = {};
113124
for (i = 0; i < pixelData.length; i += 4) {
114125
idx = pixelData[i] + (pixelData[i + 1] << 8) + (pixelData[i + 2] << 16);
115-
m_this.m_info.indices[i / 4] = idx;
116-
if (!m_this.m_info.mappedColors[idx]) {
117-
m_this.m_info.mappedColors[idx] = {first: i / 4};
126+
base.m_info.indices[i / 4] = idx;
127+
if (!base.m_info.mappedColors[idx]) {
128+
base.m_info.mappedColors[idx] = {first: i / 4};
118129
}
119-
m_this.m_info.mappedColors[idx].last = i / 4;
130+
base.m_info.mappedColors[idx].last = i / 4;
120131
}
121-
return m_this.m_info;
132+
return base.m_info;
122133
};
123134

124135
/**
@@ -127,23 +138,43 @@ var canvas_pixelmapFeature = function (arg) {
127138
* these colors, then draw the resultant image as a quad.
128139
*
129140
* @fires geo.event.pixelmap.prepared
141+
* @param {object} [quad] A quad to use as the base instead of the class
142+
* instance.
130143
*/
131-
this._computePixelmap = function () {
144+
this._computePixelmap = function (quad) {
145+
const base = quad || m_this;
132146
var data = m_this.data() || [],
133147
colorFunc = m_this.style.get('color'),
134148
i, idx, lastidx, color, pixelData, indices, mappedColors,
135149
updateFirst, updateLast = -1, update, prepared;
136150

137-
if (!m_this.m_info) {
151+
if (!m_quadFeatureInit && m_quadFeature && !quad) {
152+
m_quadFeature._hookRenderImageQuads = (quads) => {
153+
quads.forEach((quad) => {
154+
if (!quad.m_srcImage) {
155+
quad.m_srcImage = quad.image;
156+
m_this._computePixelmap(quad);
157+
quad.image = quad.m_info.context.canvas;
158+
quad._build = m_this.buildTime().timestamp();
159+
} else if (m_this.buildTime().timestamp() > quad._build) {
160+
m_this._computePixelmap(quad);
161+
quad.image = quad.m_info.context.canvas;
162+
quad._build = m_this.buildTime().timestamp();
163+
}
164+
});
165+
};
166+
m_quadFeatureInit = true;
167+
}
168+
if (!base.m_info) {
138169
m_this.indexModified(undefined, 'clear');
139-
if (!m_this._preparePixelmap()) {
170+
if (!m_this._preparePixelmap(quad)) {
140171
return;
141172
}
142173
prepared = true;
143174
}
144175
m_this.indexModified(undefined, 'clear');
145-
mappedColors = m_this.m_info.mappedColors;
146-
updateFirst = m_this.m_info.area;
176+
mappedColors = base.m_info.mappedColors;
177+
updateFirst = base.m_info.area;
147178
for (idx in mappedColors) {
148179
if (mappedColors.hasOwnProperty(idx)) {
149180
color = colorFunc(data[idx], +idx) || {};
@@ -171,8 +202,8 @@ var canvas_pixelmapFeature = function (arg) {
171202
return;
172203
}
173204
/* Update only the extent that has changed */
174-
pixelData = m_this.m_info.imageData.data;
175-
indices = m_this.m_info.indices;
205+
pixelData = base.m_info.imageData.data;
206+
indices = base.m_info.indices;
176207
for (i = updateFirst; i <= updateLast; i += 1) {
177208
idx = indices[i];
178209
if (idx !== lastidx) {
@@ -188,10 +219,13 @@ var canvas_pixelmapFeature = function (arg) {
188219
}
189220
}
190221
/* Place the updated area into the canvas */
191-
m_this.m_info.context.putImageData(
192-
m_this.m_info.imageData, 0, 0, 0, Math.floor(updateFirst / m_this.m_info.width),
193-
m_this.m_info.width, Math.ceil((updateLast + 1) / m_this.m_info.width));
222+
base.m_info.context.putImageData(
223+
base.m_info.imageData, 0, 0, 0, Math.floor(updateFirst / base.m_info.width),
224+
base.m_info.width, Math.ceil((updateLast + 1) / base.m_info.width));
194225

226+
if (quad) {
227+
return;
228+
}
195229
/* If we haven't made a quad feature, make one now. The quad feature needs
196230
* to have the canvas capability. */
197231
if (!m_quadFeature) {
@@ -206,6 +240,7 @@ var canvas_pixelmapFeature = function (arg) {
206240
position: m_this.style.get('position')})
207241
.data([{}])
208242
.draw();
243+
m_quadFeatureInit = true;
209244
}
210245
/* If we prepared the pixelmap and rendered it, send a prepared event */
211246
if (prepared) {
@@ -230,12 +265,18 @@ var canvas_pixelmapFeature = function (arg) {
230265
return m_this;
231266
};
232267

268+
if (arg.quadFeature) {
269+
m_quadFeature = arg.quadFeature;
270+
}
233271
this._init(arg);
234272
return this;
235273
};
236274

237275
inherit(canvas_pixelmapFeature, pixelmapFeature);
238276

239277
// Now register it
240-
registerFeature('canvas', 'pixelmap', canvas_pixelmapFeature);
278+
var capabilities = {};
279+
capabilities[pixelmapFeature.capabilities.lookup] = true;
280+
281+
registerFeature('canvas', 'pixelmap', canvas_pixelmapFeature, capabilities);
241282
module.exports = canvas_pixelmapFeature;

src/canvas/quadFeature.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ var canvas_quadFeature = function (arg) {
146146
}
147147
context2d.imageSmoothingEnabled = !nearestPixel;
148148
}
149+
if (m_this._hookRenderImageQuads) {
150+
m_this._hookRenderImageQuads(m_quads.imgQuads);
151+
}
149152
$.each([m_quads.imgQuads, m_quads.vidQuads], function (listidx, quadlist) {
150153
if (!quadlist) {
151154
return;

src/pixelmapFeature.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,58 @@ var pixelmapFeature = function (arg) {
261261
}
262262
} else if (m_this.m_info) {
263263
m_this._computePixelmap();
264-
}
264+
} else {
265+
m_this._computePixelmap();
266+
} // else we need to regenerate the images for canvas
265267
m_this.buildTime().modified();
266268
return m_this;
267269
};
268270

271+
/**
272+
* Given the results of the quad search, determine which pixel index is
273+
* found.
274+
*
275+
* @param {object} result An object with `index`: a list of quad indices,
276+
* `found`: a list of quads that contain the specified coordinate, and
277+
* `extra`: an object with keys that are quad indices and values that are
278+
* objects with `basis.x` and `basis.y`, values from 0 - 1 relative to
279+
* interior of the quad.
280+
* @returns {geo.feature.searchResult} An object with a list of features and
281+
* feature indices that are located at the specified point.
282+
*/
283+
this._pointSearchProcess = function (result) {
284+
// use the last index by preference, since for tile layers, this is the
285+
// topmosttile
286+
let idxIdx = result.index.length - 1;
287+
for (; idxIdx >= 0; idxIdx -= 1) {
288+
if (result.extra[result.index[idxIdx]]._quad &&
289+
result.extra[result.index[idxIdx]]._quad.image) {
290+
let img = result.extra[result.index[idxIdx]]._quad.image;
291+
if (result.extra[result.index[idxIdx]]._quad.m_srcImage) {
292+
img = result.extra[result.index[idxIdx]]._quad.m_srcImage;
293+
}
294+
const basis = result.extra[result.index[idxIdx]].basis;
295+
const x = Math.floor(basis.x * img.width);
296+
const y = Math.floor(basis.y * img.height);
297+
const canvas = document.createElement('canvas');
298+
canvas.width = canvas.height = 1;
299+
const context = canvas.getContext('2d');
300+
context.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
301+
const pixel = context.getImageData(0, 0, 1, 1).data;
302+
const idx = pixel[0] + pixel[1] * 256 + pixel[2] * 256 * 256;
303+
if (idx === 16777215) {
304+
continue;
305+
}
306+
result = {
307+
index: [idx],
308+
found: [m_this.data()[idx]]
309+
};
310+
return result;
311+
}
312+
}
313+
return {index: [], found: []};
314+
};
315+
269316
/**
270317
* Initialize.
271318
*

src/webgl/pixelmapFeature.js

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,8 @@ var webgl_pixelmapFeature = function (arg) {
4646
*/
4747
this.pointSearch = function (geo, gcs) {
4848
if (m_quadFeature && m_this.m_info) {
49-
let result = m_quadFeature.pointSearch(geo, gcs);
50-
// use the last index by preference, since for tile layers, this is the
51-
// topmosttile
52-
let idxIdx = result.index.length - 1;
53-
for (; idxIdx >= 0; idxIdx -= 1) {
54-
if (result.extra[result.index[idxIdx]]._quad &&
55-
result.extra[result.index[idxIdx]]._quad.image) {
56-
const img = result.extra[result.index[idxIdx]]._quad.image;
57-
const basis = result.extra[result.index[idxIdx]].basis;
58-
const x = Math.floor(basis.x * img.width);
59-
const y = Math.floor(basis.y * img.height);
60-
const canvas = document.createElement('canvas');
61-
canvas.width = canvas.height = 1;
62-
const context = canvas.getContext('2d');
63-
context.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
64-
const pixel = context.getImageData(0, 0, 1, 1).data;
65-
const idx = pixel[0] + pixel[1] * 256 + pixel[2] * 256 * 256;
66-
if (idx === 16777215) {
67-
continue;
68-
}
69-
result = {
70-
index: [idx],
71-
found: [m_this.data()[idx]]
72-
};
73-
return result;
74-
}
75-
}
49+
const result = m_quadFeature.pointSearch(geo, gcs);
50+
return this._pointSearchProcess(result);
7651
}
7752
return {index: [], found: []};
7853
};

0 commit comments

Comments
 (0)