forked from visgl/deck.gl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0e91aba
commit d0d8369
Showing
11 changed files
with
919 additions
and
4 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
This is a minimal standalone version of the Ascii Video Player example | ||
on [deck.gl](http://deck.gl) website. | ||
|
||
### Usage | ||
Copy the content of this folder to your project. Run | ||
``` | ||
npm install | ||
npm start | ||
``` |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* global window,document */ | ||
import React, {Component} from 'react'; | ||
import {render} from 'react-dom'; | ||
|
||
import DeckGL, {OrthographicView} from 'deck.gl'; | ||
import {isWebGL2} from 'luma.gl'; | ||
import ControlPanel from './components/control-panel'; | ||
import AsciiLayer from './ascii-layer/ascii-layer'; | ||
|
||
class Root extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
width: 500, | ||
height: 500, | ||
settings: { | ||
fontFamily: 'Monaco', | ||
isPlaying: true, | ||
sizeScale: 1, | ||
videoSource: 0 | ||
}, | ||
timestamp: Date.now(), | ||
|
||
webgl2Supported: true, | ||
videoLoading: true | ||
}; | ||
|
||
this._onResize = this._onResize.bind(this); | ||
this._onUpdate = this._onUpdate.bind(this); | ||
this._onLoad = this._onLoad.bind(this); | ||
this._onInitialize = this._onInitialize.bind(this); | ||
this._updateSettings = this._updateSettings.bind(this); | ||
} | ||
|
||
componentDidMount() { | ||
this._onResize(); | ||
|
||
window.addEventListener('resize', this._onResize); | ||
this._timer = window.requestAnimationFrame(this._onUpdate); | ||
} | ||
|
||
componentWillUnmount() { | ||
window.removeEventListener('resize', this._onResize); | ||
window.cancelAnimationFrame(this._timer); | ||
} | ||
|
||
_onResize() { | ||
this.setState({ | ||
width: window.innerWidth, | ||
height: window.innerHeight | ||
}); | ||
} | ||
|
||
_onUpdate() { | ||
if (this.state.settings.isPlaying) { | ||
this.setState({ | ||
timestamp: Date.now() | ||
}); | ||
} | ||
|
||
this._timer = window.requestAnimationFrame(this._onUpdate); | ||
} | ||
|
||
_onLoad(video) { | ||
if (video) { | ||
video.crossOrigin = 'anonymouse'; | ||
} | ||
this._video = video; | ||
} | ||
|
||
_onInitialize(gl) { | ||
this.setState({ | ||
webgl2Supported: isWebGL2(gl) | ||
}); | ||
} | ||
|
||
_updateSettings(settings) { | ||
this.setState({ | ||
settings: {...this.state.settings, ...settings} | ||
}); | ||
} | ||
|
||
_renderDeckGLOverlay({width, height, video}) { | ||
const {timestamp, settings} = this.state; | ||
|
||
return ( | ||
<DeckGL | ||
width={width || 1} | ||
height={height || 1} | ||
views={new OrthographicView()} | ||
onWebGLInitialized={this._onInitialize} | ||
> | ||
<AsciiLayer | ||
id="video" | ||
video={video} | ||
timestamp={timestamp} | ||
fontFamily={settings.fontFamily} | ||
sizeScale={settings.sizeScale} | ||
/> | ||
</DeckGL> | ||
); | ||
} | ||
|
||
render() { | ||
const {width, height, settings, webgl2Supported} = this.state; | ||
|
||
if (!webgl2Supported) { | ||
return <div className="warning">WebGL2 is not supported in your browser.</div>; | ||
} | ||
|
||
let canvasWidth = 0; | ||
let canvasHeight = 0; | ||
let canvasStyle = null; | ||
|
||
if (this._video && this._video.videoWidth) { | ||
const {videoWidth, videoHeight} = this._video; | ||
const scale = Math.min(width / videoWidth, height / videoHeight); | ||
|
||
canvasWidth = videoWidth * scale; | ||
canvasHeight = videoHeight * scale; | ||
|
||
canvasStyle = { | ||
width: canvasWidth, | ||
height: canvasHeight, | ||
top: (height - canvasHeight) / 2, | ||
left: (width - canvasWidth) / 2 | ||
}; | ||
} | ||
|
||
return ( | ||
<div id="app-container"> | ||
<video autoPlay loop id="source-video" ref={this._onLoad} /> | ||
|
||
<div id="canvas-wrapper" style={canvasStyle}> | ||
{this._renderDeckGLOverlay({ | ||
width: canvasWidth, | ||
height: canvasHeight, | ||
video: this._video | ||
})} | ||
</div> | ||
|
||
<ControlPanel {...settings} video={this._video} updateSettings={this._updateSettings} /> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
render(<Root />, document.body.appendChild(document.createElement('div'))); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
import {Texture2D, Model, Buffer, Framebuffer, Geometry, TransformFeedback, GL} from 'luma.gl'; | ||
import {sortCharactersByBrightness} from './utils'; | ||
|
||
const vs = ` | ||
#define SHADER_NAME feedback-vertex-shader | ||
uniform sampler2D video; | ||
uniform sampler2D pixelMapTexture; | ||
attribute vec2 uv; | ||
varying vec4 instanceIconFrames; | ||
varying vec4 instanceColors; | ||
float bitColor(float x) { | ||
return floor(x * 4. + 0.5) * 64.; | ||
} | ||
void main(void) { | ||
vec4 pixel = texture2D(video, uv); | ||
float luminance = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b; | ||
instanceColors = vec4( | ||
bitColor(pixel.r), | ||
bitColor(pixel.g), | ||
bitColor(pixel.b), | ||
255.0 | ||
); | ||
instanceIconFrames = texture2D(pixelMapTexture, vec2(luminance + 0.5 / 256., 0.5)); | ||
gl_Position = vec4(0.0); | ||
} | ||
`; | ||
|
||
const fs = ` | ||
#define SHADER_NAME feedback-fragment-shader | ||
#ifdef GL_ES | ||
precision highp float; | ||
#endif | ||
varying vec4 instanceIconFrames; | ||
varying vec4 instanceColors; | ||
void main(void) { | ||
gl_FragColor = vec4(0.0); | ||
} | ||
`; | ||
|
||
function getPixelMapTexture(gl, {iconMapping, iconAtlas}) { | ||
// Read the number of dark pixels by character | ||
const darkPixelsByCharacter = {}; | ||
const {width, height} = iconAtlas; | ||
const frameBuffer = new Framebuffer(gl, { | ||
width, | ||
height, | ||
attachments: { | ||
[gl.COLOR_ATTACHMENT0]: iconAtlas | ||
} | ||
}); | ||
|
||
for (const char in iconMapping) { | ||
const bbox = iconMapping[char]; | ||
|
||
const pixels = frameBuffer.readPixels({ | ||
x: bbox.x, | ||
y: height - bbox.y - bbox.height, | ||
width: bbox.width, | ||
height: bbox.height | ||
}); | ||
const len = pixels.length; | ||
let sum = 0; | ||
for (let i = 0; i < len; i += 4) { | ||
const r = pixels[i]; | ||
const a = pixels[i + 3]; | ||
if (a) { | ||
sum += 1 - r / 255 * (a / 255); | ||
} | ||
} | ||
darkPixelsByCharacter[char] = sum; | ||
} | ||
|
||
const chars = sortCharactersByBrightness(darkPixelsByCharacter); | ||
|
||
const data = new Float32Array(4 * 256); | ||
let index = 0; | ||
for (let i = 0; i < 256; i++) { | ||
const icon = iconMapping[chars[i]]; | ||
data[index++] = icon.x; | ||
data[index++] = icon.y; | ||
data[index++] = icon.width; | ||
data[index++] = icon.height; | ||
} | ||
|
||
return new Texture2D(gl, { | ||
width: 256, | ||
height: 1, | ||
dataFormat: GL.RGBA, | ||
format: GL.RGBA32F, | ||
type: GL.FLOAT, | ||
mipmaps: false, | ||
parameters: { | ||
[GL.TEXTURE_MIN_FILTER]: GL.NEAREST, | ||
[GL.TEXTURE_MAG_FILTER]: GL.NEAREST | ||
}, | ||
data | ||
}); | ||
} | ||
|
||
export default class AsciiFilter { | ||
constructor(gl, {iconMapping, iconAtlas}) { | ||
this.gl = gl; | ||
this.transformFeedback = new TransformFeedback(gl); | ||
|
||
this.model = new Model(gl, { | ||
id: 'ascii-filter', | ||
vs, | ||
fs, | ||
varyings: ['instanceIconFrames', 'instanceColors'], | ||
geometry: new Geometry({ | ||
drawMode: GL.POINTS | ||
}), | ||
vertexCount: 0, | ||
isIndexed: true | ||
}); | ||
|
||
this.videoTexture = new Texture2D(gl, { | ||
mipmaps: false, | ||
parameters: { | ||
[GL.TEXTURE_MIN_FILTER]: GL.NEAREST, | ||
[GL.TEXTURE_MAG_FILTER]: GL.NEAREST | ||
} | ||
}); | ||
this._updateDimension({width: 1, height: 1}); | ||
|
||
this.model.setUniforms({ | ||
pixelMapTexture: getPixelMapTexture(gl, {iconMapping, iconAtlas}), | ||
video: this.videoTexture | ||
}); | ||
} | ||
|
||
getBuffers({width, height, video}) { | ||
const {model, videoTexture, transformFeedback} = this; | ||
|
||
this._updateDimension({width, height, video}); | ||
videoTexture.setSubImageData({width: video.videoWidth, height: video.videoHeight, data: video}); | ||
|
||
model.draw({ | ||
uniforms: { | ||
video: videoTexture | ||
}, | ||
transformFeedback, | ||
parameters: { | ||
[GL.RASTERIZER_DISCARD]: true | ||
} | ||
}); | ||
|
||
return this.buffers; | ||
} | ||
|
||
_updateDimension({width, height, video}) { | ||
const {videoTexture, model, transformFeedback, gl} = this; | ||
|
||
if (width === videoTexture.width && height === videoTexture.height) { | ||
return; | ||
} | ||
|
||
// video.width = width; | ||
// video.height = height; | ||
videoTexture.resize({width: video.videoWidth, height: video.videoHeight}); | ||
|
||
const vertexCount = width * height; | ||
model.setVertexCount(vertexCount); | ||
|
||
// update attribute | ||
const uv = new Float32Array(2 * vertexCount); | ||
let i = 0; | ||
for (let y = 0; y < height; y++) { | ||
for (let x = 0; x < width; x++) { | ||
uv[i++] = (x + 0.5) / width; | ||
uv[i++] = (y + 0.5) / height; | ||
} | ||
} | ||
|
||
model.setAttributes({uv: {size: 2, value: uv}}); | ||
|
||
const iconFrameBuffer = new Buffer(gl, { | ||
size: 4, | ||
instanced: 1, | ||
data: new Float32Array(4 * vertexCount), | ||
usage: GL.DYNAMIC_COPY | ||
}); | ||
|
||
const colorBuffer = new Buffer(gl, { | ||
size: 4, | ||
instanced: 1, | ||
data: new Float32Array(4 * vertexCount), | ||
usage: GL.DYNAMIC_COPY | ||
}); | ||
|
||
this.buffers = { | ||
instanceIconFrames: iconFrameBuffer, | ||
instanceColors: colorBuffer | ||
}; | ||
|
||
transformFeedback.bindBuffers( | ||
{ | ||
0: iconFrameBuffer, | ||
1: colorBuffer | ||
}, | ||
{} | ||
); | ||
} | ||
} |
Oops, something went wrong.