Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 142 additions & 16 deletions js/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
let feature_params = false;

const vertexAttribs = {
"meshPosition": 0
};
Expand Down Expand Up @@ -44,6 +46,8 @@ function linkShaderProgram(gl, shaders, vertexAttribs) {
return program;
}

// TODO(#53): Filter params do not support expression in the strings

const filters = {
"Hop": {
"transparent": 0x00FF00,
Expand Down Expand Up @@ -619,6 +623,29 @@ void main() {
"Hard": {
"transparent": 0x00FF00,
"duration": 2.0 * Math.PI / 32.0,
"params": {
"zoom": {
"type": "float",
"init": 1.4,
"min": 0.0,
"max": 6.9,
"step": 0.1,
},
"intensity": {
"type": "float",
"init": 32.0,
"min": 0.0,
"max": 42.0,
"step": 1.0,
},
"amplitude": {
"type": "float",
"init": 1.0 / 8.0,
"min": 0.0,
"max": 1.0 / 2.0,
"step": 0.001,
},
},
"vertex": `#version 100
precision mediump float;

Expand All @@ -627,12 +654,13 @@ attribute vec2 meshPosition;
uniform vec2 resolution;
uniform float time;

uniform float zoom;
uniform float intensity;
uniform float amplitude;

varying vec2 uv;

void main() {
float zoom = 1.4;
float intensity = 32.0;
float amplitude = 1.0 / 8.0;
vec2 shaking = vec2(cos(intensity * time), sin(intensity * time)) * amplitude;
gl_Position = vec4(meshPosition * zoom + shaking, 0.0, 1.0);
uv = (meshPosition + 1.0) / 2.0;
Expand Down Expand Up @@ -894,20 +922,89 @@ function createTextureFromImage(gl, image) {
return textureId;
}

// TODO(#54): pre-load all of the filters and just switch between them without loading/unloading them constantly
function loadFilterProgram(gl, filter, vertexAttribs) {
let vertexShader = compileShaderSource(gl, filter.vertex, gl.VERTEX_SHADER);
let fragmentShader = compileShaderSource(gl, filter.fragment, gl.FRAGMENT_SHADER);
let id = linkShaderProgram(gl, [vertexShader, fragmentShader], vertexAttribs);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
gl.useProgram(id);

let uniforms = {
"resolution": gl.getUniformLocation(id, 'resolution'),
"time": gl.getUniformLocation(id, 'time'),
"emoteSize": gl.getUniformLocation(id, 'emoteSize'),
};

// TODO(#55): there no "reset to default" button in the params panel of a filter
let paramsPanel = div().att$("class", "widget-element");
let paramsInputs = {};

for (let paramName in filter.params) {
if (paramName in uniforms) {
throw new Error(`Redefinition of existing uniform parameter ${paramName}`);
}

switch (filter.params[paramName].type) {
case "float": {
const valuePreview = span(filter.params[paramName].init.toString());
const valueInput = input("range");

if (filter.params[paramName].min) {
valueInput.att$("min", filter.params[paramName].min);
}

if (filter.params[paramName].max) {
valueInput.att$("max", filter.params[paramName].max);
}

if (filter.params[paramName].step) {
valueInput.att$("step", filter.params[paramName].step);
}

if (filter.params[paramName].init) {
valueInput.att$("value", filter.params[paramName].init);
}

paramsInputs[paramName] = valueInput;

valueInput.oninput = function () {
valuePreview.innerText = this.value;
paramsPanel.dispatchEvent(new CustomEvent("paramsChanged"));
};

paramsPanel.appendChild(div(
span(`${paramName}: `), valuePreview,
div(valueInput),
));
} break;

default: {
throw new Error(`Filter parameters do not support type ${filter.params[paramName].type}`)
}
}

uniforms[paramName] = gl.getUniformLocation(id, paramName);
}

paramsPanel.paramsSnapshot$ = function() {
let snapshot = {};
for (let paramName in paramsInputs) {
snapshot[paramName] = {
"uniform": uniforms[paramName],
"value": paramsInputs[paramName].value
};
}
return snapshot;
};

return {
"id": id,
"resolutionUniform": gl.getUniformLocation(id, 'resolution'),
"timeUniform": gl.getUniformLocation(id, 'time'),
"emoteSizeUniform": gl.getUniformLocation(id, 'emoteSize'),
"uniforms": uniforms,
"duration": filter.duration,
"transparent": filter.transparent,
"paramsPanel": paramsPanel,
};
}

Expand Down Expand Up @@ -1003,10 +1100,9 @@ function FilterSelector() {
.att$("width", CANVAS_WIDTH)
.att$("height", CANVAS_HEIGHT);
const root = div(
div(
"Filter: ", filterList_
).att$("class", "widget-element"),
filterPreview.att$("class", "widget-element")
div("Filter: ", filterList_)
.att$("class", "widget-element"),
filterPreview.att$("class", "widget-element"),
).att$("class", "widget");

const gl = filterPreview.getContext("webgl", {antialias: false, alpha: false});
Expand Down Expand Up @@ -1053,7 +1149,23 @@ function FilterSelector() {

let emoteImage = undefined;
let emoteTexture = undefined;
let program = loadFilterProgram(gl, filterList_.selectedFilter$(), vertexAttribs);
let program = undefined;

function syncParams() {
if (program) {
const snapshot = program.paramsPanel.paramsSnapshot$();
for (let paramName in snapshot) {
gl.uniform1f(snapshot[paramName].uniform, snapshot[paramName].value);
}
}
}

program = loadFilterProgram(gl, filterList_.selectedFilter$(), vertexAttribs);
program.paramsPanel.addEventListener('paramsChanged', syncParams);
if (feature_params) {
root.appendChild(program.paramsPanel);
}
syncParams();

root.updateImage$ = function(newEmoteImage) {
emoteImage = newEmoteImage;
Expand All @@ -1066,8 +1178,18 @@ function FilterSelector() {
filterList_.addEventListener('filterChanged', function(e) {
if (program) {
gl.deleteProgram(program.id);
program.paramsPanel.removeEventListener('paramsChanged', syncParams);
if (feature_params) {
root.removeChild(program.paramsPanel);
}
}

program = loadFilterProgram(gl, e.detail.filter, vertexAttribs);
program.paramsPanel.addEventListener('paramsChanged', syncParams);
if (feature_params) {
root.appendChild(program.paramsPanel);
}
syncParams();
});

root.render$ = function (filename) {
Expand All @@ -1094,8 +1216,10 @@ function FilterSelector() {

let t = 0.0;
while (t <= duration) {
gl.uniform1f(program.timeUniform, t);
gl.uniform2f(program.resolutionUniform, CANVAS_WIDTH, CANVAS_HEIGHT);
gl.uniform1f(program.uniforms.time, t);
gl.uniform2f(program.uniforms.resolution, CANVAS_WIDTH, CANVAS_HEIGHT);
gl.uniform2f(program.uniforms.emoteSize, emoteImage.width, emoteImage.height);

gl.clearColor(0.0, 1.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, TRIANGLE_PAIR * TRIANGLE_VERTICIES);
Expand Down Expand Up @@ -1161,9 +1285,9 @@ function FilterSelector() {
gl.clear(gl.COLOR_BUFFER_BIT);

if (program && emoteImage) {
gl.uniform1f(program.timeUniform, start * 0.001);
gl.uniform2f(program.resolutionUniform, filterPreview.width, filterPreview.height);
gl.uniform2f(program.emoteSizeUniform, emoteImage.width, emoteImage.height);
gl.uniform1f(program.uniforms.time, start * 0.001);
gl.uniform2f(program.uniforms.resolution, filterPreview.width, filterPreview.height);
gl.uniform2f(program.uniforms.emoteSize, emoteImage.width, emoteImage.height);

gl.drawArrays(gl.TRIANGLES, 0, TRIANGLE_PAIR * TRIANGLE_VERTICIES);
}
Expand All @@ -1177,6 +1301,8 @@ function FilterSelector() {
}

window.onload = () => {
feature_params = new URLSearchParams(document.location.search).has("feature-params");

const imageSelector = ImageSelector();
const filterSelector = FilterSelector();
imageSelector.addEventListener('imageSelected', function(e) {
Expand Down