Skip to content

Instantly share code, notes, and snippets.

@123jimin
Last active December 25, 2024 14:07
Show Gist options
  • Save 123jimin/b0cd00970da606eb1222d48f4f1c207b to your computer and use it in GitHub Desktop.
Save 123jimin/b0cd00970da606eb1222d48f4f1c207b to your computer and use it in GitHub Desktop.
SDVXIndex Laser Color Shifter
// ==UserScript==
// @name SDVXIndex Laser Color Shifter
// @namespace https://0xF.kr/
// @version v1.0.0
// @downloadURL https://gist.githubusercontent.com/123jimin/b0cd00970da606eb1222d48f4f1c207b/raw/f444463b985313e3784c95ccc62400e7a792bd2f/laser-color-shifter.js
// @description Swaps Laser Color
// @author JiminP
// @match https://sdvxindex.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=sdvxindex.com
// @grant none
// ==/UserScript==
"use strict";
/* eslint userscripts/use-download-and-update-url: "off" */
const LASER_COLOR_SHIFTER_SRC = `
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float origHueL;
uniform float origHueR;
uniform float destHueL;
uniform float destHueR;
uniform float threshold;
vec3 rgb2hsv(vec3 c) {
float maxC = max(c.r, max(c.g, c.b));
float minC = min(c.r, min(c.g, c.b));
float delta = maxC - minC;
float h = 0.0;
float s = 0.0;
float v = maxC;
if (delta > 0.00001) {
s = delta / maxC;
if (c.r >= maxC) {
h = (c.g - c.b) / delta;
if (h < 0.0) h += 6.0;
} else if (c.g >= maxC) {
h = 2.0 + (c.b - c.r) / delta;
} else {
h = 4.0 + (c.r - c.g) / delta;
}
h *= 60.0;
}
return vec3(h, s, v);
}
vec3 hsv2rgb(vec3 c) {
float h = c.x; // [0..360)
float s = c.y; // [0..1]
float v = c.z; // [0..1]
float r, g, b;
float f = (h / 60.0) - floor(h / 60.0);
float p = v * (1.0 - s);
float q = v * (1.0 - f * s);
float t = v * (1.0 - (1.0 - f) * s);
int hi = int(floor(h / 60.0));
if (hi == 0) { r = v; g = t; b = p; }
else if (hi == 1) { r = q; g = v; b = p; }
else if (hi == 2) { r = p; g = v; b = t; }
else if (hi == 3) { r = p; g = q; b = v; }
else if (hi == 4) { r = t; g = p; b = v; }
else { r = v; g = p; b = q; }
return vec3(r, g, b);
}
void main(void) {
vec4 color = texture2D(u_texture, v_texCoord);
if (color.a < 0.0001) {
gl_FragColor = color;
return;
}
vec3 hsv = rgb2hsv(color.rgb);
float h = hsv.x;
float diffL = abs(h - origHueL);
float diffR = abs(h - origHueR);
diffL = min(diffL, 360.0 - diffL);
diffR = min(diffR, 360.0 - diffR);
if (diffL < threshold) {
h = destHueL;
} else if (diffR < threshold) {
h = destHueR;
}
hsv.x = mod(h, 360.0);
color.rgb = hsv2rgb(hsv);
gl_FragColor = color;
}
`.trim();
class LaserColorShifter {
#canvas = document.createElement('canvas');
#gl = this.#canvas.getContext('webgl');
// Original color hue, where 0 = 360 = red.
orig_hue_l = 201;
orig_hue_r = 320;
// Destination color hue.
dest_hue_l = 320;
dest_hue_r = 201;
#program;
constructor() {
const gl = this.#gl;
const vertex_shader = this.#compileSource(gl.VERTEX_SHADER, `attribute vec2 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main(void) { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; }`);
const fragment_shader = this.#compileSource(gl.FRAGMENT_SHADER, LASER_COLOR_SHIFTER_SRC);
const program = gl.createProgram();
gl.attachShader(program, vertex_shader);
gl.attachShader(program, fragment_shader);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Program link error:", gl.getProgramInfoLog(program));
return;
}
gl.useProgram(program);
this.#program = program;
this.#initPositionBuffer('a_position', [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
this.#initPositionBuffer('a_texCoord', [0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]);
const 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, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
this.#setUniform('origHueL', this.orig_hue_l);
this.#setUniform('origHueR', this.orig_hue_r);
this.#setUniform('destHueL', this.dest_hue_l);
this.#setUniform('destHueR', this.dest_hue_r);
this.#setUniform('threshold', 15.0);
}
#compileSource(shader_type, src) {
const gl = this.#gl;
const shader = gl.createShader(shader_type);
gl.shaderSource(shader, src);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("Failed to compile a shader:", gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
#initPositionBuffer(name, data) {
const gl = this.#gl;
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
const attrib_loc = gl.getAttribLocation(this.#program, name);
gl.enableVertexAttribArray(attrib_loc);
gl.vertexAttribPointer(attrib_loc, 2, gl.FLOAT, false, 0, 0);
}
#setUniform(name, value) {
const gl = this.#gl;
gl.uniform1f(gl.getUniformLocation(this.#program, name), value);
}
async apply(image_elem) {
if(!image_elem.complete) {
await new Promise((resolve) => {
const handler = () => {
image_elem.removeEventListener('load', handler);
resolve();
};
image_elem.addEventListener('load', handler);
});
}
const canvas = this.#canvas;
const gl = this.#gl;
const width = canvas.width = image_elem.naturalWidth || image_elem.width;
const height = canvas.height = image_elem.naturalHeight || image_elem.height;
if(width > 16384 || height > 16384) return;
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image_elem);
gl.viewport(0, 0, width, height);
gl.drawArrays(gl.TRIANGLES, 0, 6);
image_elem.src = canvas.toDataURL();
}
}
function adjustLaserColors() {
const target_elems = [...document.querySelectorAll("img")];
if(target_elems.length === 0) {
console.info("No target element has been found.");
return;
}
const shader = new LaserColorShifter();
for(const image_elem of target_elems) {
shader.apply(image_elem);
}
}
(function() {
adjustLaserColors();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment