Last active
December 25, 2024 14:07
-
-
Save 123jimin/b0cd00970da606eb1222d48f4f1c207b to your computer and use it in GitHub Desktop.
SDVXIndex Laser Color Shifter
This file contains hidden or 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
// ==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