diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index b09e2e3..a1fa471 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -5,6 +5,7 @@ const twgl = require('twgl.js'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); +const Rectangle = require('./Rectangle'); const PenSkin = require('./PenSkin'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); @@ -711,6 +712,73 @@ class RenderWebGL extends EventEmitter { }; } + /** + * @typedef ColorExtraction + * @property {Uint8Array} data Raw pixel data for the drawable + * @property {int} width Drawable bounding box width + * @property {int} height Drawable bounding box height + * @property {object} color Color object with RGBA properties at picked location + */ + + /** + * Return drawable pixel data and color at a given position + * @param {int} x The client x coordinate of the picking location. + * @param {int} y The client y coordinate of the picking location. + * @param {int} radius The client radius to extract pixels with. + * @return {?ColorExtraction} Data about the picked color + */ + extractColor (x, y, radius) { + const scratchX = Math.round(this._nativeSize[0] * ((x / this._gl.canvas.clientWidth) - 0.5)); + const scratchY = Math.round(-this._nativeSize[1] * ((y / this._gl.canvas.clientHeight) - 0.5)); + + const gl = this._gl; + twgl.bindFramebufferInfo(gl, this._queryBufferInfo); + + const bounds = new Rectangle(); + bounds.initFromBounds(scratchX - radius, scratchX + radius, scratchY - radius, scratchY + radius); + + const pickX = scratchX - bounds.left; + const pickY = bounds.top - scratchY; + + gl.viewport(0, 0, bounds.width, bounds.height); + const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1); + + gl.clearColor.apply(gl, this._backgroundColor); + gl.clear(gl.COLOR_BUFFER_BIT); + this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, projection); + + const data = new Uint8Array(Math.floor(bounds.width * bounds.height * 4)); + gl.readPixels(0, 0, bounds.width, bounds.height, gl.RGBA, gl.UNSIGNED_BYTE, data); + + const color = { + r: data[Math.floor(4 * (pickY * bounds.width + pickX))], + g: data[Math.floor(4 * (pickY * bounds.width + pickX)) + 1], + b: data[Math.floor(4 * (pickY * bounds.width + pickX)) + 2], + a: data[Math.floor(4 * (pickY * bounds.width + pickX)) + 3] + }; + + if (this._debugCanvas) { + this._debugCanvas.width = bounds.width; + this._debugCanvas.height = bounds.height; + const ctx = this._debugCanvas.getContext('2d'); + const imageData = ctx.createImageData(bounds.width, bounds.height); + imageData.data.set(data); + ctx.putImageData(imageData, 0, 0); + ctx.strokeStyle = 'black'; + ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`; + ctx.rect(pickX - 4, pickY - 4, 8, 8); + ctx.fill(); + ctx.stroke(); + } + + return { + data: data, + width: bounds.width, + height: bounds.height, + color: color + }; + } + /** * Get the candidate bounding box for a touching query. * @param {int} drawableID ID for drawable of query. diff --git a/src/playground/index.html b/src/playground/index.html index c9f3672..e64d503 100644 --- a/src/playground/index.html +++ b/src/playground/index.html @@ -126,6 +126,11 @@ }; } + canvas.onmousemove = function(event) { + var mousePos = getMousePos(event, canvas); + renderer.extractColor(mousePos.x, mousePos.y, 30); + }; + canvas.onclick = function(event) { var mousePos = getMousePos(event, canvas); var pickID = renderer.pick(mousePos.x, mousePos.y);