Skip to content

Commit

Permalink
Add AsciiLayer demo (visgl#1715)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress authored Apr 20, 2018
1 parent 0e91aba commit d0d8369
Show file tree
Hide file tree
Showing 11 changed files with 919 additions and 4 deletions.
9 changes: 9 additions & 0 deletions examples/showcases/ascii/README.md
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
```
148 changes: 148 additions & 0 deletions examples/showcases/ascii/app.js
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')));
214 changes: 214 additions & 0 deletions examples/showcases/ascii/ascii-layer/ascii-filter.js
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
},
{}
);
}
}
Loading

0 comments on commit d0d8369

Please sign in to comment.