// ==UserScript==
// @name Bilibili_Anime4K
// @name:zh-CN Bilibili Anime4K滤镜
// @description Bring Anime4K to Bilibili's HTML5 player to clearify 2D anime.
// @description:zh-CN 通过Anime4K滤镜让Bilibili上的2D番剧更加清晰
// @namespace http://net2cn.tk/
// @homepageURL https://github.com/net2cn/Bilibili_Anime4K/
// @supportURL https://github.com/net2cn/Bilibili_Anime4K/issues
// @version 0.4.1
// @author net2cn
// @copyright bloc97, DextroseRe, NeuroWhAI, and all contributors of Anime4K
// @match *://www.bilibili.com/video/av*
// @match *://www.bilibili.com/bangumi/play/ep*
// @match *://www.bilibili.com/bangumi/play/ss*
// @grant none
// @license MIT License
// @run-at document-idle
// ==/UserScript==
// WebGL implementation by NeuroWhAI.
// https://github.com/bloc97/Anime4K/blob/master/web/main.js
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader));
}
return shader;
}
function createProgram(gl, vertexSource, fragmentSource) {
var program = gl.createProgram();
//console.log(fragmentSource)
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(program));
}
var wrapper = { program: program };
var numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
for (var i = 0; i < numAttributes; i++) {
var attribute = gl.getActiveAttrib(program, i);
wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);
}
var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (var i$1 = 0; i$1 < numUniforms; i$1++) {
var uniform = gl.getActiveUniform(program, i$1);
wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);
}
return wrapper;
}
function createTexture(gl, filter, data, width, height) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
if (data instanceof Uint8Array) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
}
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
function bindTexture(gl, texture, unit) {
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, texture);
}
function updateTexture(gl, texture, src) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
}
function createBuffer(gl, data) {
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
return buffer;
}
function bindAttribute(gl, buffer, attribute, numComponents) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(attribute);
gl.vertexAttribPointer(attribute, numComponents, gl.FLOAT, false, 0, 0);
}
function bindFramebuffer(gl, framebuffer, texture) {
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
if (texture) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}
}
const quadVert = `
precision mediump float;
attribute vec2 a_pos;
varying vec2 v_tex_pos;
void main() {
v_tex_pos = a_pos;
gl_Position = vec4(1.0 - 2.0 * a_pos, 0, 1);
}
`;
const scaleFrag = `
precision mediump float;
uniform sampler2D u_texture;
uniform vec2 u_size;
varying vec2 v_tex_pos;
vec4 interp(const vec2 uv) {
vec2 px = 1.0 / u_size;
vec2 vc = (floor(uv * u_size)) * px;
vec2 f = fract(uv * u_size);
vec4 tl = texture2D(u_texture, vc);
vec4 tr = texture2D(u_texture, vc + vec2(px.x, 0));
vec4 bl = texture2D(u_texture, vc + vec2(0, px.y));
vec4 br = texture2D(u_texture, vc + px);
return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y);
}
void main() {
gl_FragColor = interp(1.0 - v_tex_pos);
//gl_FragColor = texture2D(u_texture, 1.0 - v_tex_pos);
}
`;
const thinLinesFrag = `
precision mediump float;
uniform sampler2D scaled_texture;
uniform float u_scale;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
#define strength (min(u_scale / 6.0, 1.0))
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texture, pos);
}
float getLum(vec4 rgb) {
return (rgb.r + rgb.r + rgb.g + rgb.g + rgb.g + rgb.b) / 6.0;
}
vec4 getLargest(vec4 cc, vec4 lightestColor, vec4 a, vec4 b, vec4 c) {
vec4 newColor = cc * (1.0 - strength) + ((a + b + c) / 3.0) * strength;
if (newColor.a > lightestColor.a) {
return newColor;
}
return lightestColor;
}
vec4 getRGBL(vec2 pos) {
return vec4(HOOKED_tex(pos).rgb, getLum(HOOKED_tex(pos)));
}
float min3v(vec4 a, vec4 b, vec4 c) {
return min(min(a.a, b.a), c.a);
}
float max3v(vec4 a, vec4 b, vec4 c) {
return max(max(a.a, b.a), c.a);
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 d = u_pt;
vec4 cc = getRGBL(HOOKED_pos);
vec4 t = getRGBL(HOOKED_pos + vec2(0.0, -d.y));
vec4 tl = getRGBL(HOOKED_pos + vec2(-d.x, -d.y));
vec4 tr = getRGBL(HOOKED_pos + vec2(d.x, -d.y));
vec4 l = getRGBL(HOOKED_pos + vec2(-d.x, 0.0));
vec4 r = getRGBL(HOOKED_pos + vec2(d.x, 0.0));
vec4 b = getRGBL(HOOKED_pos + vec2(0.0, d.y));
vec4 bl = getRGBL(HOOKED_pos + vec2(-d.x, d.y));
vec4 br = getRGBL(HOOKED_pos + vec2(d.x, d.y));
vec4 lightestColor = cc;
//Kernel 0 and 4
float maxDark = max3v(br, b, bl);
float minLight = min3v(tl, t, tr);
if (minLight > cc.a && minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, tl, t, tr);
} else {
maxDark = max3v(tl, t, tr);
minLight = min3v(br, b, bl);
if (minLight > cc.a && minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, br, b, bl);
}
}
//Kernel 1 and 5
maxDark = max3v(cc, l, b);
minLight = min3v(r, t, tr);
if (minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, r, t, tr);
} else {
maxDark = max3v(cc, r, t);
minLight = min3v(bl, l, b);
if (minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, bl, l, b);
}
}
//Kernel 2 and 6
maxDark = max3v(l, tl, bl);
minLight = min3v(r, br, tr);
if (minLight > cc.a && minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, r, br, tr);
} else {
maxDark = max3v(r, br, tr);
minLight = min3v(l, tl, bl);
if (minLight > cc.a && minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, l, tl, bl);
}
}
//Kernel 3 and 7
maxDark = max3v(cc, l, t);
minLight = min3v(r, br, b);
if (minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, r, br, b);
} else {
maxDark = max3v(cc, r, b);
minLight = min3v(t, l, tl);
if (minLight > maxDark) {
lightestColor = getLargest(cc, lightestColor, t, l, tl);
}
}
gl_FragColor = lightestColor;
}
`;
const lumaFrag = `
precision mediump float;
uniform sampler2D scaled_texuture;
uniform sampler2D post_kernel_texture;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texuture, pos);
}
float getLum(vec4 rgb) {
return (rgb.r + rgb.r + rgb.g + rgb.g + rgb.g + rgb.b) / 6.0;
}
void main() { //Save lum on OUTPUT
vec2 HOOKED_pos = v_tex_pos;
vec4 rgb = HOOKED_tex(HOOKED_pos);
float lum = getLum(rgb);
gl_FragColor = vec4(lum);
}
`;
const lumaGausXFrag = `
precision mediump float;
uniform sampler2D scaled_texture;
uniform sampler2D post_kernel_texture;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texture, pos);
}
vec4 LUMA_tex(vec2 pos){
return texture2D(post_kernel_texture, pos);
}
float lumGaussian5(vec2 pos, vec2 d) {
float g = LUMA_tex(pos - (d * 2.0)).x * 0.187691;
g = g + LUMA_tex(pos - d).x * 0.206038;
g = g + LUMA_tex(pos).x * 0.212543;
g = g + LUMA_tex(pos + d).x * 0.206038;
g = g + LUMA_tex(pos + (d * 2.0)).x * 0.187691;
return clamp(g, 0.0, 1.0); //Clamp for sanity check
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 HOOKED_pt = u_pt;
float g = lumGaussian5(HOOKED_pos, vec2(HOOKED_pt.x, 0));
gl_FragColor = vec4(LUMA_tex(HOOKED_pos).x, g, LUMA_tex(HOOKED_pos).zw);
}
`;
const lumaGausYFrag = `
precision mediump float;
uniform sampler2D scaled_texture;
uniform sampler2D post_kernel_texture;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texture, pos);
}
vec4 LUMAG_tex(vec2 pos){
return texture2D(post_kernel_texture, pos);
}
float lumGaussian5(vec2 pos, vec2 d) {
float g = LUMAG_tex(pos - (d * 2.0)).x * 0.187691;
g = g + LUMAG_tex(pos - d).x * 0.206038;
g = g + LUMAG_tex(pos).x * 0.212543;
g = g + LUMAG_tex(pos + d).x * 0.206038;
g = g + LUMAG_tex(pos + (d * 2.0)).x * 0.187691;
return clamp(g, 0.0, 1.0); //Clamp for sanity check
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 HOOKED_pt = u_pt;
float g = lumGaussian5(HOOKED_pos, vec2(0, HOOKED_pt.y));
gl_FragColor = vec4(LUMAG_tex(HOOKED_pos).x, g, LUMAG_tex(HOOKED_pos).zw);
}
`;
const lineDetectFrag = `
precision mediump float;
uniform sampler2D scaled_texture;
uniform sampler2D post_kernel_texture;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
#define BlendColorDodgef(base, blend) (((blend) == 1.0) ? (blend) : min((base) / (1.0 - (blend)), 1.0))
#define BlendColorDividef(top, bottom) (((bottom) == 1.0) ? (bottom) : min((top) / (bottom), 1.0))
// Component wise blending
#define Blend(base, blend, funcf) vec3(funcf(base.r, blend.r), funcf(base.g, blend.g), funcf(base.b, blend.b))
#define BlendColorDodge(base, blend) Blend(base, blend, BlendColorDodgef)
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texture, pos);
}
vec4 POSTKERNEL_tex(vec2 pos){
return texture2D(post_kernel_texture, pos);
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
float lum = clamp(POSTKERNEL_tex(HOOKED_pos).x, 0.001, 0.999);
float lumg = clamp(POSTKERNEL_tex(HOOKED_pos).y, 0.001, 0.999);
float pseudolines = BlendColorDividef(lum, lumg);
pseudolines = 1.0 - clamp(pseudolines - 0.05, 0.0, 1.0);
gl_FragColor = vec4(pseudolines, 0, 0, 0);
}
`;
const lineGausXFrag = `
precision mediump float;
uniform sampler2D scaled_texture;
uniform sampler2D post_kernel_texture;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texture, pos);
}
vec4 LUMAG_tex(vec2 pos){
return texture2D(post_kernel_texture, pos);
}
float lumGaussian5(vec2 pos, vec2 d) {
float g = LUMAG_tex(pos - (d * 2.0)).x * 0.187691;
g = g + LUMAG_tex(pos - d).x * 0.206038;
g = g + LUMAG_tex(pos).x * 0.212543;
g = g + LUMAG_tex(pos + d).x * 0.206038;
g = g + LUMAG_tex(pos + (d * 2.0)).x * 0.187691;
return clamp(g, 0.0, 1.0); //Clamp for sanity check
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 HOOKED_pt = u_pt;
float g = lumGaussian5(HOOKED_pos, vec2(HOOKED_pt.x, 0.0));
gl_FragColor = vec4(g, 0, 0, 0);
}
`;
const lineGausYFrag = `
precision mediump float;
uniform sampler2D scaled_texture;
uniform sampler2D post_kernel_texture;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(scaled_texture, pos);
}
vec4 LUMAG_tex(vec2 pos){
return texture2D(post_kernel_texture, pos);
}
float lumGaussian5(vec2 pos, vec2 d) {
float g = LUMAG_tex(pos - (d * 2.0)).x * 0.187691;
g = g + LUMAG_tex(pos - d).x * 0.206038;
g = g + LUMAG_tex(pos).x * 0.212543;
g = g + LUMAG_tex(pos + d).x * 0.206038;
g = g + LUMAG_tex(pos + (d * 2.0)).x * 0.187691;
return clamp(g, 0.0, 1.0); //Clamp for sanity check
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 HOOKED_pt = u_pt;
float g = lumGaussian5(HOOKED_pos, vec2(0.0, HOOKED_pt.y));
gl_FragColor = vec4(g, 0, 0, 0);
}
`;
const gradFrag = `
precision mediump float;
uniform sampler2D u_texture;
uniform sampler2D u_textureTemp;
uniform vec2 u_pt;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(u_texture, 1.0 - pos);
}
vec4 POSTKERNEL_tex(vec2 pos) {
return texture2D(u_textureTemp, 1.0 - pos);
}
vec4 getRGBL(vec2 pos) {
return vec4(HOOKED_tex(pos).rgb, POSTKERNEL_tex(pos).x);
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 d = u_pt;
//[tl t tr]
//[ l cc r]
//[bl b br]
vec4 cc = getRGBL(HOOKED_pos);
vec4 t = getRGBL(HOOKED_pos + vec2(0.0, -d.y));
vec4 tl = getRGBL(HOOKED_pos + vec2(-d.x, -d.y));
vec4 tr = getRGBL(HOOKED_pos + vec2(d.x, -d.y));
vec4 l = getRGBL(HOOKED_pos + vec2(-d.x, 0.0));
vec4 r = getRGBL(HOOKED_pos + vec2(d.x, 0.0));
vec4 b = getRGBL(HOOKED_pos + vec2(0.0, d.y));
vec4 bl = getRGBL(HOOKED_pos + vec2(-d.x, d.y));
vec4 br = getRGBL(HOOKED_pos + vec2(d.x, d.y));
//Horizontal Gradient
//[-1 0 1]
//[-2 0 2]
//[-1 0 1]
float xgrad = (-tl.a + tr.a - l.a - l.a + r.a + r.a - bl.a + br.a);
//Vertical Gradient
//[-1 -2 -1]
//[ 0 0 0]
//[ 1 2 1]
float ygrad = (-tl.a - t.a - t.a - tr.a + bl.a + b.a + b.a + br.a);
gl_FragColor = vec4(1.0 - clamp(sqrt(xgrad * xgrad + ygrad * ygrad), 0.0, 1.0));
}
`;
const refineFrag = `
precision mediump float;
uniform sampler2D u_texture;
uniform sampler2D u_textureTemp;
uniform vec2 u_pt;
uniform float u_scale;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(u_texture, vec2(pos.x, 1.0 - pos.y));
}
vec4 POSTKERNEL_tex(vec2 pos) {
return texture2D(u_textureTemp, vec2(pos.x, 1.0 - pos.y));
}
#define LINE_DETECT_MUL 8.0
#define LINE_DETECT_THRESHOLD 0.2
#define strength (min(u_scale, 1.0))
#define lineprob (POSTKERNEL_tex(v_tex_pos).y)
vec4 getAverage(vec4 cc, vec4 a, vec4 b, vec4 c) {
float prob = clamp(lineprob * LINE_DETECT_MUL, 0.0, 1.0);
if (prob < LINE_DETECT_THRESHOLD) {
prob = 0.0;
}
float realstrength = clamp(strength * prob, 0.0, 1.0);
return cc * (1.0 - realstrength) + ((a + b + c) / 3.0) * realstrength;
}
vec4 getRGBL(vec2 pos) {
return vec4(HOOKED_tex(pos).rgb, POSTKERNEL_tex(pos).z);
}
float min3v(vec4 a, vec4 b, vec4 c) {
return min(min(a.a, b.a), c.a);
}
float max3v(vec4 a, vec4 b, vec4 c) {
return max(max(a.a, b.a), c.a);
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 d = u_pt;
vec4 cc = getRGBL(HOOKED_pos);
vec4 t = getRGBL(HOOKED_pos + vec2(0.0, -d.y));
vec4 tl = getRGBL(HOOKED_pos + vec2(-d.x, -d.y));
vec4 tr = getRGBL(HOOKED_pos + vec2(d.x, -d.y));
vec4 l = getRGBL(HOOKED_pos + vec2(-d.x, 0.0));
vec4 r = getRGBL(HOOKED_pos + vec2(d.x, 0.0));
vec4 b = getRGBL(HOOKED_pos + vec2(0.0, d.y));
vec4 bl = getRGBL(HOOKED_pos + vec2(-d.x, d.y));
vec4 br = getRGBL(HOOKED_pos + vec2(d.x, d.y));
//Kernel 0 and 4
float maxDark = max3v(br, b, bl);
float minLight = min3v(tl, t, tr);
if (minLight > cc.a && minLight > maxDark) {
gl_FragColor = getAverage(cc, tl, t, tr);
return;
} else {
maxDark = max3v(tl, t, tr);
minLight = min3v(br, b, bl);
if (minLight > cc.a && minLight > maxDark) {
gl_FragColor = getAverage(cc, br, b, bl);
return;
}
}
//Kernel 1 and 5
maxDark = max3v(cc, l, b);
minLight = min3v(r, t, tr);
if (minLight > maxDark) {
gl_FragColor = getAverage(cc, r, t, tr);
return;
} else {
maxDark = max3v(cc, r, t);
minLight = min3v(bl, l, b);
if (minLight > maxDark) {
gl_FragColor = getAverage(cc, bl, l, b);
return;
}
}
//Kernel 2 and 6
maxDark = max3v(l, tl, bl);
minLight = min3v(r, br, tr);
if (minLight > cc.a && minLight > maxDark) {
gl_FragColor = getAverage(cc, r, br, tr);
return;
} else {
maxDark = max3v(r, br, tr);
minLight = min3v(l, tl, bl);
if (minLight > cc.a && minLight > maxDark) {
gl_FragColor = getAverage(cc, l, tl, bl);
return;
}
}
//Kernel 3 and 7
maxDark = max3v(cc, l, t);
minLight = min3v(r, br, b);
if (minLight > maxDark) {
gl_FragColor = getAverage(cc, r, br, b);
return;
} else {
maxDark = max3v(cc, r, b);
minLight = min3v(t, l, tl);
if (minLight > maxDark) {
gl_FragColor = getAverage(cc, t, l, tl);
return;
}
}
gl_FragColor = cc;
}
`;
const fxaaFrag = `
precision mediump float;
uniform sampler2D u_texture;
uniform sampler2D u_textureTemp;
uniform vec2 u_pt;
uniform float u_scale;
varying vec2 v_tex_pos;
vec4 HOOKED_tex(vec2 pos) {
return texture2D(u_texture, vec2(pos.x, 1.0 - pos.y));
}
vec4 POSTKERNEL_tex(vec2 pos) {
return texture2D(u_textureTemp, vec2(pos.x, 1.0 - pos.y));
}
#define FXAA_MIN (1.0 / 128.0)
#define FXAA_MUL (1.0 / 8.0)
#define FXAA_SPAN 8.0
#define LINE_DETECT_MUL 4.0
#define LINE_DETECT_THRESHOLD 0.2
#define strength (min(u_scale, 1.0))
#define lineprob (POSTKERNEL_tex(v_tex_pos).y)
vec4 getAverage(vec4 cc, vec4 xc) {
float prob = clamp(lineprob * LINE_DETECT_MUL, 0.0, 1.0);
if (prob < LINE_DETECT_THRESHOLD) {
prob = 0.0;
}
float realstrength = clamp(strength * prob, 0.0, 1.0);
return cc * (1.0 - realstrength) + xc * realstrength;
}
float getLum(vec4 rgb) {
return (rgb.r + rgb.r + rgb.g + rgb.g + rgb.g + rgb.b) / 6.0;
}
void main() {
vec2 HOOKED_pos = v_tex_pos;
vec2 d = u_pt;
vec4 cc = HOOKED_tex(HOOKED_pos);
vec4 xc = cc;
float t = POSTKERNEL_tex(HOOKED_pos + vec2(0, -d.y)).x;
float l = POSTKERNEL_tex(HOOKED_pos + vec2(-d.x, 0)).x;
float r = POSTKERNEL_tex(HOOKED_pos + vec2(d.x, 0)).x;
float b = POSTKERNEL_tex(HOOKED_pos + vec2(0, d.y)).x;
float tl = POSTKERNEL_tex(HOOKED_pos + vec2(-d.x, -d.y)).x;
float tr = POSTKERNEL_tex(HOOKED_pos + vec2(d.x, -d.y)).x;
float bl = POSTKERNEL_tex(HOOKED_pos + vec2(-d.x, d.y)).x;
float br = POSTKERNEL_tex(HOOKED_pos + vec2(d.x, d.y)).x;
float cl = POSTKERNEL_tex(HOOKED_pos).x;
float minl = min(cl, min(min(tl, tr), min(bl, br)));
float maxl = max(cl, max(max(tl, tr), max(bl, br)));
vec2 dir = vec2(- tl - tr + bl + br, tl - tr + bl - br);
float dirReduce = max((tl + tr + bl + br) *
(0.25 * FXAA_MUL), FXAA_MIN);
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = min(vec2(FXAA_SPAN, FXAA_SPAN),
max(vec2(-FXAA_SPAN, -FXAA_SPAN),
dir * rcpDirMin)) * d;
vec4 rgbA = 0.5 * (
HOOKED_tex(HOOKED_pos + dir * -(1.0/6.0)) +
HOOKED_tex(HOOKED_pos + dir * (1.0/6.0)));
vec4 rgbB = rgbA * 0.5 + 0.25 * (
HOOKED_tex(HOOKED_pos + dir * -0.5) +
HOOKED_tex(HOOKED_pos + dir * 0.5));
//vec4 luma = vec4(0.299, 0.587, 0.114, 0.0);
//float lumb = dot(rgbB, luma);
float lumb = getLum(rgbB);
if ((lumb < minl) || (lumb > maxl)) {
xc = rgbA;
} else {
xc = rgbB;
}
gl_FragColor = getAverage(cc, xc);
}
`;
const drawFrag = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_tex_pos;
void main() {
vec4 color = texture2D(u_texture, vec2(v_tex_pos.x, 1.0 - v_tex_pos.y));
gl_FragColor = color;
}
`;
function Scaler(gl) {
this.gl = gl;
this.inputTex = null;
this.inputMov = null;
this.inputWidth = 0;
this.inputHeight = 0;
this.quadBuffer = createBuffer(gl, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]));
this.framebuffer = gl.createFramebuffer();
console.log('Compiling shaders...')
this.scaleProgram = createProgram(gl, quadVert, scaleFrag);
this.thinLinesProgram = createProgram(gl, quadVert, thinLinesFrag);
this.lumaProgram = createProgram(gl, quadVert, lumaFrag);
this.lumaGausXProgram = createProgram(gl, quadVert, lumaGausXFrag);
this.lumaGausYProgram = createProgram(gl, quadVert, lumaGausYFrag);
this.lineDetectProgram = createProgram(gl, quadVert, lineDetectFrag);
this.lineGausXProgram = createProgram(gl, quadVert, lineGausXFrag);
this.lineGausYProgram = createProgram(gl, quadVert, lineGausYFrag);
this.gradProgram = createProgram(gl, quadVert, gradFrag);
this.refineProgram = createProgram(gl, quadVert, refineFrag);
this.fxaaProgram = createProgram(gl, quadVert, fxaaFrag);
this.drawProgram = createProgram(gl, quadVert, drawFrag);
this.postKernelTexture = null;
this.postKernelTexture2 = null;
this.scale = 1.0;
}
Scaler.prototype.inputImage = function (img) {
const gl = this.gl;
this.inputWidth = img.width;
this.inputHeight = img.height;
this.inputTex = createTexture(gl, gl.LINEAR, img);
this.inputMov = null;
}
Scaler.prototype.inputVideo = function (mov) {
const gl = this.gl;
const width = mov.videoWidth;
const height = mov.videoHeight;
this.inputWidth = width;
this.inputHeight = height;
let emptyPixels = new Uint8Array(width * height * 4);
this.inputTex = createTexture(gl, gl.LINEAR, emptyPixels, width, height);
this.inputMov = mov;
}
Scaler.prototype.resize = function (scale) {
const gl = this.gl;
const width = Math.round(this.inputWidth * scale);
const height = Math.round(this.inputHeight * scale);
gl.canvas.width = width;
gl.canvas.height = height;
let emptyPixels = new Uint8Array(width * height * 4);
this.scaleTexture = createTexture(gl, gl.LINEAR, emptyPixels, width, height);
this.scaleTexture2 = createTexture(gl, gl.LINEAR, emptyPixels, width, height);
this.postKernelTexture = createTexture(gl, gl.LINEAR, emptyPixels, width, height);
this.postKernelTexture2 = createTexture(gl, gl.LINEAR, emptyPixels, width, height);
}
Scaler.prototype.render = function () {
if (!this.inputTex) {
return;
}
const gl = this.gl;
const scalePgm = this.scaleProgram;
const thinLinesPgm = this.thinLinesProgram;
const lumaPgm = this.lumaProgram;
const lumaGausXPgm = this.lumaGausXProgram;
const lumaGausYPgm = this.lumaGausYProgram;
const lineDetectPgm = this.lineDetectProgram;
const lineGausXPgm = this.lineGausXProgram;
const lineGausYPgm = this.lineGausYProgram;
const gradPgm = this.gradProgram;
const refinePgm = this.refineProgram;
const fxaaPgm = this.fxaaProgram;
const drawPgm = this.drawProgram;
// Nasty trick to fix video quailty changing bug.
if (gl.getError() == gl.INVALID_VALUE) {
console.log('glError detected! Fetching new viedo tag... (This may happen due to resolution change)')
let newMov = getNewVideoTag()
this.inputVideo(newMov)
}
if (this.inputMov) {
updateTexture(gl, this.inputTex, this.inputMov);
}
// Automatic change scale according to original video resolution.
let newScale = 1440 / this.inputMov.videoHeight;
if (this.scale != newScale){
this.scale = newScale;
console.log('Setting scale to ' + this.scale);
}
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.STENCIL_TEST);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// First upscaling with Bicubic interpolation.
// Upscaling
bindFramebuffer(gl, this.framebuffer, this.scaleTexture);
gl.useProgram(scalePgm.program);
bindAttribute(gl, this.quadBuffer, scalePgm.a_pos, 2);
bindTexture(gl, this.inputTex, 0);
gl.uniform1i(scalePgm.u_texture, 0);
gl.uniform2f(scalePgm.u_size, this.inputWidth, this.inputHeight);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Scaled: scaleTexture
// Thin Lines
bindFramebuffer(gl, this.framebuffer, this.scaleTexture2);
gl.useProgram(thinLinesPgm.program);
bindAttribute(gl, this.quadBuffer, thinLinesPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture, 0);
gl.uniform1i(thinLinesPgm.scaled_texture, 0);
gl.uniform1f(thinLinesPgm.u_scale, this.scale);
gl.uniform2f(thinLinesPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Scaled: scaleTexture2
// Compute Luminance
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture);
gl.useProgram(lumaPgm.program);
bindAttribute(gl, this.quadBuffer, lumaPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
gl.uniform1i(lumaPgm.scaled_texture, 0);
gl.uniform2f(lumaPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Scaled: scaleTexture2 (unchanged)
// PostKernel: postKernelTexture (luminance)
// Compute Luminance Gaussian X
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture2);
gl.useProgram(lumaGausXPgm.program);
bindAttribute(gl, this.quadBuffer, lumaGausXPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture, 1);
gl.uniform1i(lumaGausXPgm.scaled_texture, 0);
gl.uniform1i(lumaGausXPgm.post_kernel_texture, 1);
gl.uniform2f(lumaGausXPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: postKernelTexture2
// Compute Luminance Gaussian Y
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture);
gl.useProgram(lumaGausYPgm.program);
bindAttribute(gl, this.quadBuffer, lumaGausYPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture2, 1);
gl.uniform1i(lumaGausYPgm.scaled_texture, 0);
gl.uniform1i(lumaGausYPgm.post_kernel_texture, 1);
gl.uniform2f(lumaGausYPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: postKernelTexture
// Line detect
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture2);
gl.useProgram(lineDetectPgm.program);
bindAttribute(gl, this.quadBuffer, lumaGausYPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture, 1);
gl.uniform1i(lineDetectPgm.scaled_texture, 0);
gl.uniform1i(lineDetectPgm.post_kernel_texture, 1);
gl.uniform2f(lineDetectPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: postKernelTexture2
// Compute Line Gaussian X
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture);
gl.useProgram(lineGausXPgm.program);
bindAttribute(gl, this.quadBuffer, lineGausXPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture2, 1);
gl.uniform1i(lineGausXPgm.scaled_texture, 0);
gl.uniform1i(lineGausXPgm.post_kernel_texture, 1);
gl.uniform2f(lineGausXPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: postKernelTexture
// Compute Line Gaussian Y
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture2);
gl.useProgram(lineGausYPgm.program);
bindAttribute(gl, this.quadBuffer, lineGausYPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture, 1);
gl.uniform1i(lineGausYPgm.scaled_texture, 0);
gl.uniform1i(lineGausYPgm.post_kernel_texture, 1);
gl.uniform2f(lineGausYPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: postKernelTexture2
// Compute Gradient
bindFramebuffer(gl, this.framebuffer, this.postKernelTexture);
gl.useProgram(gradPgm.program);
bindAttribute(gl, this.quadBuffer, gradPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture2, 1);
gl.uniform1i(gradPgm.scaleFrag, 0);
gl.uniform1i(gradPgm.post_kernel_texture, 1);
gl.uniform2f(gradPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: postKernelTexture
// Refine
bindFramebuffer(gl, this.framebuffer, this.scaleTexture);
gl.useProgram(refinePgm.program);
bindAttribute(gl, this.quadBuffer, refinePgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
bindTexture(gl, this.postKernelTexture, 1);
gl.uniform1i(refinePgm.u_texture, 0);
gl.uniform1i(refinePgm.u_textureTemp, 1);
gl.uniform1f(refinePgm.u_scale, this.scale);
gl.uniform2f(refinePgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: scaleTexture
// FXAA
bindFramebuffer(gl, this.framebuffer, this.scaleTexture2);
gl.useProgram(fxaaPgm.program);
bindAttribute(gl, this.quadBuffer, fxaaPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture, 0);
bindTexture(gl, this.postKernelTexture, 1);
gl.uniform1i(fxaaPgm.u_texture, 0);
gl.uniform1i(fxaaPgm.u_textureTemp, 1);
gl.uniform1f(fxaaPgm.u_scale, this.scale);
gl.uniform2f(fxaaPgm.u_pt, 1.0 / gl.canvas.width, 1.0 / gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// PostKernel: scaleTexture2
// Draw
bindFramebuffer(gl, null);
gl.useProgram(drawPgm.program);
bindAttribute(gl, this.quadBuffer, drawPgm.a_pos, 2);
bindTexture(gl, this.scaleTexture2, 0);
gl.uniform1i(drawPgm.u_texture, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
// Parameters.
let scaler = null;
let movOrig = null;
let board = null;
let scale = 2.0;
function insertController() {
// let controllerText =
// `
// <div class="anime4k-panel" id="anime4k-panel">
// <span>启用</span>
// <input class="anime4k-switch" type="checkbox" checked="">
// <span>缩放大小</span>
// <input type="number" id="anime4k-scale" value="1.25" min="1.0" max="4.0" step="0.1">
// </div>
// `
}
function initializeVideoTag() {
// Find our video tag as input
movOrig = document.getElementsByClassName('bilibili-player-video');
// I don't know why this happen. Not a clue at all.
if (movOrig == null) {
console.log("Can't find video tag! This could happen if this anime needs VIP.")
return
}
movOrig = movOrig[0].firstChild
// Hide it, we don't need it to be displayed.
movOrig.style.display = 'none'
}
function getNewVideoTag() {
if (movOrig.src == "") {
// Find our video tag as input
movOrig = document.getElementsByClassName('bilibili-player-video');
// I don't know why this happen. Not a clue at all.
if (movOrig == null) {
console.log("Can't find video tag! This could happen if this anime needs VIP.")
return
}
movOrig = movOrig[0].lastChild
// Hide it, we don't need it to be displayed.
movOrig.style.display = 'none'
}
scaler.scale = scale;
return movOrig
}
function createCanvas() {
// Create a canvas (since video tag do not support WebGL).
let div = document.getElementsByClassName('bilibili-player-video')[0]
// I don't know why this happen. Not a clue at all.
if (div == undefined) {
console.log("Can't find video tag! This could happen if this anime needs VIP.")
return
}
board = document.createElement('canvas');
// Make it visually fill the positioned parent
board.style.width = '100%';
board.style.height = '100%';
// ...then set the internal size to match
board.width = board.offsetWidth;
board.height = board.offsetHeight;
// Add it back to the div where contains the video tag we use as input.
div.appendChild(board)
}
function doFilter() {
// Setting our parameters for filtering.
// scale: multipliers that we need to zoom in.
// Here's the fun part. We create a pixel shader for our canvas
const gl = board.getContext('webgl');
movOrig.addEventListener('loadedmetadata', function () {
scaler = new Scaler(gl);
scaler.inputVideo(movOrig);
scaler.resize(scale);
scaler.scale = scale;
}, true);
movOrig.addEventListener('error', function () {
alert("Can't get video, sorry.");
}, true);
// Do it! Filter it! Profit!
function render() {
if (scaler) {
scaler.scale;
scaler.render();
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
(function () {
console.log('Bilibili_Anime4K starting...')
insertController()
console.log('Injecting canvas...')
createCanvas()
console.log('Hiding elements...')
initializeVideoTag()
console.log('Enabling filter...')
doFilter()
})();