-
-
Save liquidzym/1020597 to your computer and use it in GitHub Desktop.
water canvas by Almer Thie http://code.almeros.com
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Water Canvas by Almer Thie (http://code.almeros.com). | |
* Description: A realtime water ripple effect on an HTML5 canvas. | |
* Copyright 2010 Almer Thie. All rights reserved. | |
* | |
* Example: http://code.almeros.com/code-examples/water-effect-canvas/ | |
* Tutorial: http://code.almeros.com/water-ripple-canvas-and-javascript | |
*/ | |
//////////////////////////////////////////////////////////////////////////////// | |
// WaterCanvas object // | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* This view object is responsible for applying the water ripple data state to an image and therefor | |
* is also responsible for the refraction effect of the light through the water. | |
* | |
* @param {Number} width The width in pixels this canvas should be. | |
* @param {Number} hight The hight in pixels this canvas should be. | |
* @param {String} documentElement The div element ID to insert the canvas into. | |
* @param {Object} waterModel A reference to a WaterModel object previously created. | |
* @param {Object} props A key/value object which may contain the following properties: | |
* backgroundImageUrl: | |
* A relative URL to an image on your webserver. This cannot be a URL to an | |
* image on another domain. The HTML5 Canvas element follows the Same Origin Policy. | |
* lightRefraction: | |
* Sets how many pixels the refraction of light uses. | |
* lightReflection: | |
* Sets the amount of color highlighting. | |
* maxFps: | |
* Maximum Frames Per Second; The maximum reachable FPS highly depends on the system and | |
* browser the water canvas is running on. You can't control that, but you can control the maximum | |
* FPS to keep systems from overloading, trying to reach the highest FPS. | |
* showStats: | |
* When set to true, it shows "FPS Canvas: " plus the current FPS of this view on the canvas. | |
* | |
* @constructor | |
*/ | |
WaterCanvas = function(width, height, documentElement, waterModel, props) { | |
// If a certain property is not set, use a default value | |
props = props || {}; | |
this.backgroundImageUrl = props.backgroundImageUrl || null; | |
this.lightRefraction = props.lightRefraction || 9.0; | |
this.lightReflection = props.lightReflection || 0.1; | |
this.showStats = props.showStats || false; | |
this.width = width; | |
this.height = height; | |
this.documentElement = document.getElementById(documentElement); | |
this.waterModel = waterModel; | |
this.canvas = document.createElement('canvas'); | |
this.canvasHelp = document.createElement('canvas'); | |
if (!this.canvas.getContext || !this.canvasHelp.getContext) { | |
alert("You need a browser that supports the HTML5 canvas tag."); | |
return; // No point to continue | |
} | |
this.ctx = this.canvas.getContext('2d'); | |
this.ctxHelp = this.canvasHelp.getContext('2d'); | |
this.setSize(width, height); | |
this.documentElement.appendChild(this.canvas); | |
// Find out the FPS at certain intervals | |
this.fps = -1; | |
if (this.showStats) { | |
this.fpsCounter = 0; | |
this.prevMs = 0; | |
var self = this; | |
setInterval(function() { | |
self.findFps(); | |
}, 1000); | |
} | |
// Start the animation | |
this.drawNextFrame(); | |
} | |
/** | |
* Sets the size of the canvas. | |
* | |
* @param {Number} width The width in pixels this canvas should be. | |
* @param {Number} hight The hight in pixels this canvas should be. | |
*/ | |
WaterCanvas.prototype.setSize = function(width, height) { | |
this.width = width; | |
this.height = height; | |
this.canvas.setAttribute('width', this.width); | |
this.canvas.setAttribute('height', this.height); | |
this.canvasHelp.setAttribute('width', this.width); | |
this.canvasHelp.setAttribute('height', this.height); | |
this.setBackground(this.backgroundImageUrl); | |
} | |
/** | |
* Sets the image to perform the water rippling effect on. | |
* | |
* Use an image from the same server as where this script lives. This is needed since browsers use the | |
* Same Origin Policy for savety reasons. If an empty URL is given, a standard canvas will be shown. | |
* | |
* @param {String} backgroundImageUrl (Relative) URL to an image on the same webserver as this script. | |
*/ | |
WaterCanvas.prototype.setBackground = function(backgroundImageUrl) { | |
this.backgroundImageUrl = backgroundImageUrl == "" ? null : backgroundImageUrl; | |
this.pixelsIn = null; | |
if (this.backgroundImageUrl != null) { | |
// Background image loading | |
this.backgroundImg = new Image(); | |
var self = this; | |
this.backgroundImg.onload = function() { | |
self.ctxHelp.drawImage(self.backgroundImg, 0, 0, self.width, self.height); | |
// Get the canvas pixel data | |
var imgDataIn = self.ctxHelp.getImageData(0, 0, self.width, self.height); | |
self.pixelsIn = imgDataIn.data; | |
// Also paint it on the output canvas | |
self.ctx.putImageData(imgDataIn, 0, 0); | |
} | |
this.backgroundImg.src = this.backgroundImageUrl; | |
} else { | |
var radgrad = pointerCtx.createRadialGradient(this.width / 2, this.height / 2, 0, this.width / 2, this.height / 2, this.height / 2); | |
radgrad.addColorStop(0, '#4af'); | |
radgrad.addColorStop(1, '#000'); | |
this.ctxHelp.fillStyle = radgrad; | |
this.ctxHelp.fillRect(0, 0, this.width, this.height); | |
this.ctxHelp.shadowColor = "white"; | |
this.ctxHelp.shadowOffsetX = 0; | |
this.ctxHelp.shadowOffsetY = 0; | |
this.ctxHelp.shadowBlur = 10; | |
this.ctxHelp.textBaseline = "top"; | |
this.ctxHelp.font = 'normal 200 45px verdana'; | |
this.ctxHelp.fillStyle = "white"; | |
this.ctxHelp.fillText("Water Canvas", 10, (this.height / 2) - 40); | |
this.ctxHelp.font = 'normal 200 12px verdana'; | |
this.ctxHelp.fillText("Move your mouse over this canvas to move the water.", 10, (this.height / 2) + 10); | |
this.ctxHelp.fillText("By Almeros 2010, See http://code.almeros.com", 10, (this.height / 2) + 30); | |
// Get the canvas pixel data | |
var imgDataIn = this.ctxHelp.getImageData(0, 0, this.width, this.height); | |
this.pixelsIn = imgDataIn.data; | |
// Also paint it on the output canvas | |
this.ctx.putImageData(imgDataIn, 0, 0); | |
} | |
} | |
/** | |
* Renders the next frame and draws it on the canvas. | |
* Also handles calling itself again via requestAnim(ation)Frame. | |
* | |
* @private | |
*/ | |
WaterCanvas.prototype.drawNextFrame = function() { | |
if (this.pixelsIn == null || !this.waterModel.isEvolving()) { | |
// Wait some time and try again | |
var self = this; | |
setTimeout(function() { | |
self.drawNextFrame(); | |
}, 50); | |
// Nothing else to do for now | |
return; | |
} | |
// Make the canvas give us a CanvasDataArray. | |
// Creating an array ourselves is slow!!! | |
// https://developer.mozilla.org/en/HTML/Canvas/Pixel_manipulation_with_canvas | |
var imgDataOut = this.ctx.getImageData(0, 0, this.width, this.height); | |
var pixelsOut = imgDataOut.data; | |
for (var i = 0; n = pixelsOut.length, i < n; i += 4) { | |
var pixel = i / 4; | |
var x = pixel % this.width; | |
var y = (pixel - x) / this.width; | |
var strength = this.waterModel.getWater(x, y); | |
// Refraction of light in water | |
var refraction = Math.round(strength * this.lightRefraction); | |
var xPix = x + refraction; | |
var yPix = y + refraction; | |
if (xPix < 0) xPix = 0; | |
if (yPix < 0) yPix = 0; | |
if (xPix > this.width - 1) xPix = this.width - 1; | |
if (yPix > this.height - 1) yPix = this.height - 1; | |
// Get the pixel from input | |
var iPix = ((yPix * this.width) + xPix) * 4; | |
var red = this.pixelsIn[iPix]; | |
var green = this.pixelsIn[iPix + 1]; | |
var blue = this.pixelsIn[iPix + 2]; | |
// Set the pixel to output | |
strength *= this.lightReflection; | |
strength += 1.0; | |
pixelsOut[i] = red *= strength; | |
pixelsOut[i + 1] = green *= strength; | |
pixelsOut[i + 2] = blue *= strength; | |
pixelsOut[i + 3] = 255; // alpha | |
} | |
this.ctx.putImageData(imgDataOut, 0, 0); | |
if (this.showStats) { | |
this.fpsCounter++; | |
this.ctx.textBaseline = "top"; | |
this.ctx.font = 'normal 200 10px arial'; | |
this.ctx.fillStyle = "white"; | |
this.ctx.fillText("FPS Canvas: " + this.getFps(), 10, 10); | |
this.ctx.fillText("FPS Water: " + this.waterModel.getFps(), 10, 20); | |
this.ctx.shadowColor = "black"; | |
this.ctx.shadowOffsetX = 0; | |
this.ctx.shadowOffsetY = 0; | |
this.ctx.shadowBlur = 2; | |
} | |
// Make the browser call this function at a new render frame | |
var self = this; // For referencing 'this' in internal eventListeners | |
requestAnimFrame(function() { | |
self.drawNextFrame() | |
}, this.canvas); | |
} | |
/** | |
* Determine the Frames Per Second. | |
* Called at regular intervals by an internal setInterval. | |
* | |
* @private | |
*/ | |
WaterCanvas.prototype.findFps = function() { | |
if (!this.showStats) return; | |
var nowMs = new Date().getTime(); | |
var diffMs = nowMs - this.prevMs; | |
this.fps = Math.round(((this.fpsCounter * 1000) / diffMs) * 10.0) / 10.0; | |
this.prevMs = nowMs; | |
this.fpsCounter = 0; | |
} | |
/** | |
* @returns The Frames Per Second that was last rendered. | |
*/ | |
WaterCanvas.prototype.getFps = function() { | |
return this.fps; | |
} | |
/** | |
* Sets the a amount of refraction of light through the water. | |
* | |
* @param {Number} lightRefraction The a amount of refraction of light. | |
*/ | |
WaterCanvas.prototype.setLightRefraction = function(lightRefraction) { | |
this.lightRefraction = lightRefraction; | |
} | |
/** | |
* Sets the a amount of reflection of light on the water. | |
* | |
* @param {Number} lightRefraction The a amount of reflection of light. | |
*/ | |
WaterCanvas.prototype.setLightReflection = function(lightReflection) { | |
this.lightReflection = lightReflection; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// WaterModel object // | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* Model object that is responsible for holding, manipulating and updating the water ripple data. | |
* | |
* @param {Number} width The width in pixels this model should work with. | |
* @param {Number} hight The hight in pixels this model should work with. | |
* @param {Object} props A key/value object which may contain the following properties: | |
* resolution: | |
* Sets the pixelsize to use in the model. The higher hte better the performance, but | |
* you may want to use interpolation to prefent visible block artifacts. | |
* interpolate: | |
* When the resolution is set higher then 1.0, you can set this to true to use interpolation. | |
* damping: | |
* Effectively sets how long water ripples travel. | |
* clipping: | |
* Multiple waves add up. This may create a wave that's to high or low. The clipping value sets | |
* the absolute maximum a water pixel value may have in the model. | |
* evolveThreshold: | |
* To prevent this script from always taking up CPU cycles, even when no water rippling | |
* is going on in the model (your website is on a non visible tab) you can set a threshold. When | |
* no water pixel is above this absolute value, the WaterModel and the WaterCanvas will both stop | |
* rendering until new waves are created by the user. | |
* maxFps: | |
* Maximum Frames Per Second; The maximum reachable FPS highly depends on the system and browser | |
* the water canvas is running on. You can't control that, but you can control the maximum FPS to | |
* keep systems from overloading, trying to reach the highest FPS. | |
* showStats: | |
* When set to true, it shows "FPS Water: " plus the current FPS of this model on the canvas. | |
* | |
* @constructor | |
*/ | |
WaterModel = function(width, height, props) { | |
// If a certain property is not set, use a default value | |
props = props || {}; | |
this.resolution = props.resolution || 2.0; | |
this.interpolate = props.interpolate || false; | |
this.damping = props.damping || 0.985; | |
this.clipping = props.clipping || 5; | |
this.maxFps = props.maxFps || 30; | |
this.showStats = props.showStats || false; | |
this.evolveThreshold = props.evolveThreshold || 0.05; | |
this.width = Math.ceil(width / this.resolution); | |
this.height = Math.ceil(height / this.resolution); | |
// Create water model 2D arrays | |
this.resetSizeAndResolution(width, height, this.resolution) | |
this.swapMap; | |
this.setMaxFps(this.maxFps); | |
this.evolving = false; // Holds whether it's needed to render frames | |
// Find out the FPS at certain intervals | |
this.fps = -1; | |
if (this.showStats) { | |
this.fpsCounter = 0; | |
this.prevMs = 0; | |
var self = this; | |
setInterval(function() { | |
self.findFps(); | |
}, 1000); | |
} | |
} | |
/** | |
* Gets the (interpolated) water value of an coordinate. | |
* | |
* @param {Number} x The X position. | |
* @param {Number} y The Y position. | |
* | |
* @returns A float value representing the hight of the water. | |
*/ | |
WaterModel.prototype.getWater = function(x, y) { | |
xTrans = x / this.resolution; | |
yTrans = y / this.resolution; | |
if (!this.interpolate || this.resolution == 1.0) { | |
xF = Math.floor(xTrans); | |
yF = Math.floor(yTrans); | |
if (xF > this.width - 1 || yF > this.height - 1) return 0.0; | |
return this.depthMap1[xF][yF]; | |
} | |
// Else use Bilinear Interpolation | |
xF = Math.floor(xTrans); | |
yF = Math.floor(yTrans); | |
xC = Math.ceil(xTrans); | |
yC = Math.ceil(yTrans); | |
if (xC > this.width - 1 || yC > this.height - 1) return 0.0; | |
// Now get 4 points from the array | |
var br = this.depthMap1[xF][yF]; | |
var bl = this.depthMap1[xC][yF]; | |
var tr = this.depthMap1[xF][yC]; | |
var tl = this.depthMap1[xC][yC]; | |
// http://tech-algorithm.com/articles/bilinear-image-scaling/ | |
// D C | |
// Y | |
// B A | |
// Y = A(1-w)(1-h) + B(w)(1-h) + C(h)(1-w) + Dwh | |
var xChange = xC - xTrans; | |
var yChange = yC - yTrans; | |
var intpVal = | |
tl * (1 - xChange) * (1 - yChange) + tr * (xChange) * (1 - yChange) + bl * (yChange) * (1 - xChange) + br * xChange * yChange; | |
return intpVal; | |
} | |
/** | |
* Sets bilinear interpolation on or off. Interpolation will give a more smooth effect | |
* when a higher resolution is used, but needs CPU resources for that. | |
* | |
* @param {Boolean} interpolate Whether to use interpolation or not | |
*/ | |
WaterModel.prototype.setInterpolation = function(interpolate) { | |
this.interpolate = interpolate; | |
} | |
/** | |
* Gets the (interpolated) water value of an coordinate. | |
* | |
* @param {Number} x The X position. The center of where the array2d will be placed. | |
* @param {Number} y The Y position. The center of where the array2d will be placed. | |
* @param {Number} pressure The factor to multiply the array2d values with while adding the array2d to the model. | |
* @param {Array} array2d A 2D array containing float values between -1.0 and 1.0 in a pattern. | |
*/ | |
WaterModel.prototype.touchWater = function(x, y, pressure, array2d) { | |
this.evolving = true; | |
x = Math.floor(x / this.resolution); | |
y = Math.floor(y / this.resolution); | |
// Place the array2d in the center of the mouse position | |
if (array2d.length > 4 || array2d[0].length > 4) { | |
x -= array2d.length / 2; | |
y -= array2d[0].length / 2; | |
} | |
if (x < 0) x = 0; | |
if (y < 0) y = 0; | |
if (x > this.width) x = this.width; | |
if (y > this.height) y = this.height; | |
// Big pixel block | |
for (var i = 0; i < array2d.length; i++) { | |
for (var j = 0; j < array2d[0].length; j++) { | |
if (x + i >= 0 && y + j >= 0 && x + i <= this.width - 1 && y + j <= this.height - 1) { | |
this.depthMap1[x + i][y + j] -= array2d[i][j] * pressure; | |
} | |
} | |
} | |
} | |
/** | |
* Renders the next frame in the model. The water ripples will be evolved one step. | |
* Called at regular intervals by an internal setInterval. | |
* | |
* @private | |
*/ | |
WaterModel.prototype.renderNextFrame = function() { | |
if (!this.evolving) return; | |
this.evolving = false; | |
for (var x = 0; x < this.width; x++) { | |
for (var y = 0; y < this.height; y++) { | |
// Handle borders correctly | |
var val = (x == 0 ? 0 : this.depthMap1[x - 1][y]) + (x == this.width - 1 ? 0 : this.depthMap1[x + 1][y]) + (y == 0 ? 0 : this.depthMap1[x][y - 1]) + (y == this.height - 1 ? 0 : this.depthMap1[x][y + 1]); | |
// Damping | |
val = ((val / 2.0) - this.depthMap2[x][y]) * this.damping; | |
// Clipping prevention | |
if (val > this.clipping) val = this.clipping; | |
if (val < -this.clipping) val = -this.clipping; | |
// Evolve check | |
if (Math.abs(val) > this.evolveThreshold) this.evolving = true; | |
this.depthMap2[x][y] = val; | |
} | |
} | |
// Swap buffer references | |
this.swapMap = this.depthMap1; | |
this.depthMap1 = this.depthMap2; | |
this.depthMap2 = this.swapMap; | |
this.fpsCounter++; | |
} | |
/** | |
* Tells if the WaterModel is currently evolving. When all postions in the model are below a threshold | |
* (evolveThreshold), evolving will be set to false. This saves resources, especially when the canvas | |
* is not visible on screen. | |
* | |
* @returns A boolean that tells if the WaterModel is currently in evolving state. | |
*/ | |
WaterModel.prototype.isEvolving = function() { | |
return this.evolving; | |
} | |
/** | |
* Determine the Frames Per Second. | |
* Called at regular intervals by an internal setInterval. | |
* | |
* @private | |
*/ | |
WaterModel.prototype.findFps = function() { | |
if (!this.showStats) return; | |
var nowMs = new Date().getTime(); | |
var diffMs = nowMs - this.prevMs; | |
this.fps = Math.round(((this.fpsCounter * 1000) / diffMs) * 10.0) / 10.0; | |
this.prevMs = nowMs; | |
this.fpsCounter = 0; | |
} | |
/** | |
* @returns The Frames Per Second that was last rendered. | |
*/ | |
WaterModel.prototype.getFps = function() { | |
return this.fps; | |
} | |
/** | |
* Sets the maximum frames per second to render. Use this to set a limit and release resources for other processes. | |
* | |
* @param {Number} maxFps The maximum frames per second to render. | |
*/ | |
WaterModel.prototype.setMaxFps = function(maxFps) { | |
this.maxFps = maxFps; | |
clearInterval(this.maxFpsInterval); | |
// Updating of the animation | |
var self = this; // For referencing 'this' in internal eventListeners | |
if (this.maxFps > 0) { | |
this.maxFpsInterval = setInterval(function() { | |
self.renderNextFrame(); | |
}, 1000 / this.maxFps); | |
} | |
} | |
/** | |
* Effectively sets how long water ripples travel. | |
* | |
* @param {Number} damping The amount of strength to pass on from postion to surrounding positions. | |
*/ | |
WaterModel.prototype.setDamping = function(damping) { | |
this.damping = damping; | |
} | |
/** | |
* Effectively sets how long water ripples travel. | |
* | |
* @param {Number} width The width in pixels this model should work with. | |
* @param {Number} hight The hight in pixels this model should work with. | |
* @param {Number} resolution The pixel size of a models position. The higher the resolution, the less positions to render, the faster. | |
*/ | |
WaterModel.prototype.resetSizeAndResolution = function(width, height, resolution) { | |
this.width = Math.ceil(width / resolution); | |
this.height = Math.ceil(height / resolution); | |
this.resolution = resolution; | |
this.depthMap1 = new Array(this.width); | |
this.depthMap2 = new Array(this.width); | |
for (var x = 0; x < this.width; x++) { | |
this.depthMap1[x] = new Array(this.height); | |
this.depthMap2[x] = new Array(this.height); | |
for (var y = 0; y < this.height; y++) { | |
this.depthMap1[x][y] = 0.0; | |
this.depthMap2[x][y] = 0.0; | |
} | |
} | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// Util functions // | |
//////////////////////////////////////////////////////////////////////////////// | |
/** | |
* A class to mimic rain on the given waterModel with raindrop2dArray's as raindrops. | |
*/ | |
RainMaker = function(width, height, waterModel, raindrop2dArray) { | |
this.width = width; | |
this.height = height; | |
this.waterModel = waterModel; | |
this.raindrop2dArray = raindrop2dArray; | |
this.rainMinPressure = 1; | |
this.rainMaxPressure = 3; | |
} | |
RainMaker.prototype.raindrop = function() { | |
var x = Math.floor(Math.random() * this.width); | |
var y = Math.floor(Math.random() * this.height); | |
this.waterModel.touchWater(x, y, this.rainMinPressure + Math.random() * this.rainMaxPressure, this.raindrop2dArray); | |
} | |
RainMaker.prototype.setRaindropsPerSecond = function(rps) { | |
this.rps = rps; | |
clearInterval(this.rainInterval); | |
if (this.rps > 0) { | |
var self = this; | |
this.rainInterval = setInterval(function() { | |
self.raindrop(); | |
}, 1000 / this.rps); | |
} | |
} | |
RainMaker.prototype.setRainMinPressure = function(rainMinPressure) { | |
this.rainMinPressure = rainMinPressure; | |
} | |
RainMaker.prototype.setRainMaxPressure = function(rainMaxPressure) { | |
this.rainMaxPressure = rainMaxPressure; | |
} | |
/** | |
* Enables mouse interactivity by adding event listeners to the given documentElement and | |
* using the mouse coordinates to 'touch' the water. | |
*/ | |
function enableMouseInteraction(waterModel, documentElement) { | |
var mouseDown = false; | |
var canvasHolder = document.getElementById(documentElement); | |
canvasHolder.addEventListener("mousedown", function(e) { | |
mouseDown = true; | |
var x = (e.clientX - canvasHolder.offsetLeft) + document.body.scrollLeft + document.documentElement.scrollLeft; | |
var y = (e.clientY - canvasHolder.offsetTop) + document.body.scrollTop + document.documentElement.scrollTop; | |
waterModel.touchWater(x, y, 1.5, mouseDown ? finger : pixel); | |
}, false); | |
canvasHolder.addEventListener("mouseup", function(e) { | |
mouseDown = false; | |
}, false); | |
canvasHolder.addEventListener("mousemove", function(e) { | |
var x = (e.clientX - canvasHolder.offsetLeft) + document.body.scrollLeft + document.documentElement.scrollLeft; | |
var y = (e.clientY - canvasHolder.offsetTop) + document.body.scrollTop + document.documentElement.scrollTop; | |
// mozPressure: https://developer.mozilla.org/en/DOM/Event/UIEvent/MouseEvent | |
waterModel.touchWater(x, y, 1.5, mouseDown ? finger : pixel); | |
}, false); | |
} | |
/** | |
* Creates a canvas with a radial gradient from white in the center to black on the outside. | |
*/ | |
function createRadialCanvas(width, height) { | |
// Create a canvas | |
var pointerCanvas = document.createElement('canvas'); | |
pointerCanvas.setAttribute('width', width); | |
pointerCanvas.setAttribute('height', height); | |
pointerCtx = pointerCanvas.getContext('2d'); | |
// Create a drawing on the canvas | |
var radgrad = pointerCtx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, height / 2); | |
radgrad.addColorStop(0, '#fff'); | |
radgrad.addColorStop(1, '#000'); | |
pointerCtx.fillStyle = radgrad; | |
pointerCtx.fillRect(0, 0, width, height); | |
return pointerCanvas; | |
} | |
/** | |
* Creates a 2D pointer array from a given canvas with a grayscale image on it. | |
* This canvas image is then converted to a 2D array with values between -1.0 and 0.0. | |
* | |
* Example: | |
* var array2d = [ | |
* [0.5, 1.0, 0.5], | |
* [1.0, 1.0, 1.0], | |
* [0.5, 1.0, 0.5] | |
* ]; | |
*/ | |
function create2DArray(canvas) { | |
var width = canvas.width; | |
var height = canvas.height; | |
// Create an empty 2D array | |
var pointerArray = new Array(width); | |
for (var x = 0; x < width; x++) { | |
pointerArray[x] = new Array(height); | |
for (var y = 0; y < height; y++) { | |
pointerArray[x][y] = 0.0; | |
} | |
} | |
// Convert gray scale canvas to 2D array | |
var pointerCtx = canvas.getContext('2d'); | |
var imgData = pointerCtx.getImageData(0, 0, width, height); | |
var pixels = imgData.data; | |
for (var i = 0; n = pixels.length, i < n; i += 4) { | |
// Get the pixel from input | |
var pixVal = pixels[i]; // only use red | |
var arrVal = pixVal / 255.0; | |
var pixel = i / 4; | |
var x = pixel % width; | |
var y = (pixel - x) / width; | |
pointerArray[x][y] = arrVal; | |
} | |
return pointerArray; | |
} | |
// requestAnimFrame (NB: NOT requestAnimationFrame) will be used, | |
// so make sure it's available. Credits @mrdoob | |
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ | |
window.requestAnimFrame = (function() { | |
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || | |
function( /* function */ callback, /* DOMElement */ element) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment