diff options
Diffstat (limited to 'packages/frontend/src/utility/image-effector')
37 files changed, 940 insertions, 882 deletions
diff --git a/packages/frontend/src/utility/image-effector/fxs/blockNoise.glsl b/packages/frontend/src/utility/image-effector/fxs/blockNoise.glsl new file mode 100644 index 0000000000..84c4ecbed4 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/blockNoise.glsl @@ -0,0 +1,43 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform int u_amount; +uniform float u_shiftStrengths[128]; +uniform vec2 u_shiftOrigins[128]; +uniform vec2 u_shiftSizes[128]; +uniform float u_channelShift; +out vec4 out_color; + +void main() { + // TODO: ピクセル毎に計算する必要はないのでuniformにする + float aspect_ratio = min(in_resolution.x, in_resolution.y) / max(in_resolution.x, in_resolution.y); + float aspect_ratio_x = in_resolution.x > in_resolution.y ? 1.0 : aspect_ratio; + float aspect_ratio_y = in_resolution.x < in_resolution.y ? 1.0 : aspect_ratio; + + float v = 0.0; + + for (int i = 0; i < u_amount; i++) { + if ( + in_uv.x * aspect_ratio_x > ((u_shiftOrigins[i].x * aspect_ratio_x) - u_shiftSizes[i].x) && + in_uv.x * aspect_ratio_x < ((u_shiftOrigins[i].x * aspect_ratio_x) + u_shiftSizes[i].x) && + in_uv.y * aspect_ratio_y > ((u_shiftOrigins[i].y * aspect_ratio_y) - u_shiftSizes[i].y) && + in_uv.y * aspect_ratio_y < ((u_shiftOrigins[i].y * aspect_ratio_y) + u_shiftSizes[i].y) + ) { + v += u_shiftStrengths[i]; + } + } + + float r = texture(in_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r; + float g = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).g; + float b = texture(in_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b; + float a = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).a; + out_color = vec4(r, g, b, a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/blockNoise.ts b/packages/frontend/src/utility/image-effector/fxs/blockNoise.ts index 7e09524c10..355ab4536c 100644 --- a/packages/frontend/src/utility/image-effector/fxs/blockNoise.ts +++ b/packages/frontend/src/utility/image-effector/fxs/blockNoise.ts @@ -4,49 +4,10 @@ */ import seedrandom from 'seedrandom'; +import shader from './blockNoise.glsl'; import { defineImageEffectorFx } from '../ImageEffector.js'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform int u_amount; -uniform float u_shiftStrengths[128]; -uniform vec2 u_shiftOrigins[128]; -uniform vec2 u_shiftSizes[128]; -uniform float u_channelShift; -out vec4 out_color; - -void main() { - // TODO: ピクセル毎に計算する必要はないのでuniformにする - float aspect_ratio = min(in_resolution.x, in_resolution.y) / max(in_resolution.x, in_resolution.y); - float aspect_ratio_x = in_resolution.x > in_resolution.y ? 1.0 : aspect_ratio; - float aspect_ratio_y = in_resolution.x < in_resolution.y ? 1.0 : aspect_ratio; - - float v = 0.0; - - for (int i = 0; i < u_amount; i++) { - if ( - in_uv.x * aspect_ratio_x > ((u_shiftOrigins[i].x * aspect_ratio_x) - u_shiftSizes[i].x) && - in_uv.x * aspect_ratio_x < ((u_shiftOrigins[i].x * aspect_ratio_x) + u_shiftSizes[i].x) && - in_uv.y * aspect_ratio_y > ((u_shiftOrigins[i].y * aspect_ratio_y) - u_shiftSizes[i].y) && - in_uv.y * aspect_ratio_y < ((u_shiftOrigins[i].y * aspect_ratio_y) + u_shiftSizes[i].y) - ) { - v += u_shiftStrengths[i]; - } - } - - float r = texture(in_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r; - float g = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).g; - float b = texture(in_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b; - float a = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).a; - out_color = vec4(r, g, b, a); -} -`; - export const FX_blockNoise = defineImageEffectorFx({ id: 'blockNoise', name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.blockNoise, diff --git a/packages/frontend/src/utility/image-effector/fxs/blur.glsl b/packages/frontend/src/utility/image-effector/fxs/blur.glsl new file mode 100644 index 0000000000..e591267887 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/blur.glsl @@ -0,0 +1,78 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform vec2 u_offset; +uniform vec2 u_scale; +uniform bool u_ellipse; +uniform float u_angle; +uniform float u_radius; +uniform int u_samples; +out vec4 out_color; + +void main() { + float angle = -(u_angle * PI); + vec2 centeredUv = in_uv - vec2(0.5, 0.5) - u_offset; + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ) + u_offset; + + bool isInside = false; + if (u_ellipse) { + vec2 norm = (rotatedUV - u_offset) / u_scale; + isInside = dot(norm, norm) <= 1.0; + } else { + isInside = rotatedUV.x > u_offset.x - u_scale.x && rotatedUV.x < u_offset.x + u_scale.x && rotatedUV.y > u_offset.y - u_scale.y && rotatedUV.y < u_offset.y + u_scale.y; + } + + if (!isInside) { + out_color = texture(in_texture, in_uv); + return; + } + + vec4 result = vec4(0.0); + float totalSamples = 0.0; + + // Make blur radius resolution-independent by using a percentage of image size + // This ensures consistent visual blur regardless of image resolution + float referenceSize = min(in_resolution.x, in_resolution.y); + float normalizedRadius = u_radius / 100.0; // Convert radius to percentage (0-15 -> 0-0.15) + vec2 blurOffset = vec2(normalizedRadius) / in_resolution * referenceSize; + + // Calculate how many samples to take in each direction + // This determines the grid density, not the blur extent + int sampleRadius = int(sqrt(float(u_samples)) / 2.0); + + // Sample in a grid pattern within the specified radius + for (int x = -sampleRadius; x <= sampleRadius; x++) { + for (int y = -sampleRadius; y <= sampleRadius; y++) { + // Normalize the grid position to [-1, 1] range + float normalizedX = float(x) / float(sampleRadius); + float normalizedY = float(y) / float(sampleRadius); + + // Scale by radius to get the actual sampling offset + vec2 offset = vec2(normalizedX, normalizedY) * blurOffset; + vec2 sampleUV = in_uv + offset; + + // Only sample if within texture bounds + if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 && sampleUV.y >= 0.0 && sampleUV.y <= 1.0) { + result += texture(in_texture, sampleUV); + totalSamples += 1.0; + } + } + } + + out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/blur.ts b/packages/frontend/src/utility/image-effector/fxs/blur.ts index fa215fd3e4..40f51fa646 100644 --- a/packages/frontend/src/utility/image-effector/fxs/blur.ts +++ b/packages/frontend/src/utility/image-effector/fxs/blur.ts @@ -4,83 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './blur.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform vec2 u_offset; -uniform vec2 u_scale; -uniform bool u_ellipse; -uniform float u_angle; -uniform float u_radius; -uniform int u_samples; -out vec4 out_color; - -void main() { - float angle = -(u_angle * PI); - vec2 centeredUv = in_uv - vec2(0.5, 0.5) - u_offset; - vec2 rotatedUV = vec2( - centeredUv.x * cos(angle) - centeredUv.y * sin(angle), - centeredUv.x * sin(angle) + centeredUv.y * cos(angle) - ) + u_offset; - - bool isInside = false; - if (u_ellipse) { - vec2 norm = (rotatedUV - u_offset) / u_scale; - isInside = dot(norm, norm) <= 1.0; - } else { - isInside = rotatedUV.x > u_offset.x - u_scale.x && rotatedUV.x < u_offset.x + u_scale.x && rotatedUV.y > u_offset.y - u_scale.y && rotatedUV.y < u_offset.y + u_scale.y; - } - - if (!isInside) { - out_color = texture(in_texture, in_uv); - return; - } - - vec4 result = vec4(0.0); - float totalSamples = 0.0; - - // Make blur radius resolution-independent by using a percentage of image size - // This ensures consistent visual blur regardless of image resolution - float referenceSize = min(in_resolution.x, in_resolution.y); - float normalizedRadius = u_radius / 100.0; // Convert radius to percentage (0-15 -> 0-0.15) - vec2 blurOffset = vec2(normalizedRadius) / in_resolution * referenceSize; - - // Calculate how many samples to take in each direction - // This determines the grid density, not the blur extent - int sampleRadius = int(sqrt(float(u_samples)) / 2.0); - - // Sample in a grid pattern within the specified radius - for (int x = -sampleRadius; x <= sampleRadius; x++) { - for (int y = -sampleRadius; y <= sampleRadius; y++) { - // Normalize the grid position to [-1, 1] range - float normalizedX = float(x) / float(sampleRadius); - float normalizedY = float(y) / float(sampleRadius); - - // Scale by radius to get the actual sampling offset - vec2 offset = vec2(normalizedX, normalizedY) * blurOffset; - vec2 sampleUV = in_uv + offset; - - // Only sample if within texture bounds - if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 && sampleUV.y >= 0.0 && sampleUV.y <= 1.0) { - result += texture(in_texture, sampleUV); - totalSamples += 1.0; - } - } - } - - out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv); -} -`; - export const FX_blur = defineImageEffectorFx({ id: 'blur', name: i18n.ts._imageEffector._fxs.blur, diff --git a/packages/frontend/src/utility/image-effector/fxs/checker.glsl b/packages/frontend/src/utility/image-effector/fxs/checker.glsl new file mode 100644 index 0000000000..09d11c15d2 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/checker.glsl @@ -0,0 +1,43 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_angle; +uniform float u_scale; +uniform vec3 u_color; +uniform float u_opacity; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); + float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); + + float angle = -(u_angle * PI); + vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ); + + float fmodResult = mod(floor(u_scale * rotatedUV.x) + floor(u_scale * rotatedUV.y), 2.0); + float fin = max(sign(fmodResult), 0.0); + + out_color = vec4( + mix(in_color.r, u_color.r, fin * u_opacity), + mix(in_color.g, u_color.g, fin * u_opacity), + mix(in_color.b, u_color.b, fin * u_opacity), + in_color.a + ); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/checker.ts b/packages/frontend/src/utility/image-effector/fxs/checker.ts index c48f73acbd..7d1938eeb7 100644 --- a/packages/frontend/src/utility/image-effector/fxs/checker.ts +++ b/packages/frontend/src/utility/image-effector/fxs/checker.ts @@ -4,48 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './checker.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_angle; -uniform float u_scale; -uniform vec3 u_color; -uniform float u_opacity; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); - float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); - - float angle = -(u_angle * PI); - vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); - vec2 rotatedUV = vec2( - centeredUv.x * cos(angle) - centeredUv.y * sin(angle), - centeredUv.x * sin(angle) + centeredUv.y * cos(angle) - ); - - float fmodResult = mod(floor(u_scale * rotatedUV.x) + floor(u_scale * rotatedUV.y), 2.0); - float fin = max(sign(fmodResult), 0.0); - - out_color = vec4( - mix(in_color.r, u_color.r, fin * u_opacity), - mix(in_color.g, u_color.g, fin * u_opacity), - mix(in_color.b, u_color.b, fin * u_opacity), - in_color.a - ); -} -`; - export const FX_checker = defineImageEffectorFx({ id: 'checker', name: i18n.ts._imageEffector._fxs.checker, diff --git a/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.glsl b/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.glsl new file mode 100644 index 0000000000..60bb4f5318 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.glsl @@ -0,0 +1,49 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +out vec4 out_color; +uniform float u_amount; +uniform float u_start; +uniform bool u_normalize; + +void main() { + int samples = 64; + float r_strength = 1.0; + float g_strength = 1.5; + float b_strength = 2.0; + + vec2 size = vec2(in_resolution.x, in_resolution.y); + + vec4 accumulator = vec4(0.0); + float normalisedValue = length((in_uv - 0.5) * 2.0); + float strength = clamp((normalisedValue - u_start) * (1.0 / (1.0 - u_start)), 0.0, 1.0); + + vec2 vector = (u_normalize ? normalize(in_uv - vec2(0.5)) : in_uv - vec2(0.5)); + vec2 velocity = vector * strength * u_amount; + + vec2 rOffset = -vector * strength * (u_amount * r_strength); + vec2 gOffset = -vector * strength * (u_amount * g_strength); + vec2 bOffset = -vector * strength * (u_amount * b_strength); + + for (int i = 0; i < samples; i++) { + accumulator.r += texture(in_texture, in_uv + rOffset).r; + rOffset -= velocity / float(samples); + + accumulator.g += texture(in_texture, in_uv + gOffset).g; + gOffset -= velocity / float(samples); + + accumulator.b += texture(in_texture, in_uv + bOffset).b; + bOffset -= velocity / float(samples); + } + + out_color = vec4(vec3(accumulator / float(samples)), 1.0); +} + diff --git a/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts b/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts index 4adb7ce91e..ed4d134251 100644 --- a/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts +++ b/packages/frontend/src/utility/image-effector/fxs/chromaticAberration.ts @@ -4,53 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './chromaticAberration.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -out vec4 out_color; -uniform float u_amount; -uniform float u_start; -uniform bool u_normalize; - -void main() { - int samples = 64; - float r_strength = 1.0; - float g_strength = 1.5; - float b_strength = 2.0; - - vec2 size = vec2(in_resolution.x, in_resolution.y); - - vec4 accumulator = vec4(0.0); - float normalisedValue = length((in_uv - 0.5) * 2.0); - float strength = clamp((normalisedValue - u_start) * (1.0 / (1.0 - u_start)), 0.0, 1.0); - - vec2 vector = (u_normalize ? normalize(in_uv - vec2(0.5)) : in_uv - vec2(0.5)); - vec2 velocity = vector * strength * u_amount; - - vec2 rOffset = -vector * strength * (u_amount * r_strength); - vec2 gOffset = -vector * strength * (u_amount * g_strength); - vec2 bOffset = -vector * strength * (u_amount * b_strength); - - for (int i = 0; i < samples; i++) { - accumulator.r += texture(in_texture, in_uv + rOffset).r; - rOffset -= velocity / float(samples); - - accumulator.g += texture(in_texture, in_uv + gOffset).g; - gOffset -= velocity / float(samples); - - accumulator.b += texture(in_texture, in_uv + bOffset).b; - bOffset -= velocity / float(samples); - } - - out_color = vec4(vec3(accumulator / float(samples)), 1.0); -} -`; - export const FX_chromaticAberration = defineImageEffectorFx({ id: 'chromaticAberration', name: i18n.ts._imageEffector._fxs.chromaticAberration, diff --git a/packages/frontend/src/utility/image-effector/fxs/colorAdjust.glsl b/packages/frontend/src/utility/image-effector/fxs/colorAdjust.glsl new file mode 100644 index 0000000000..2d0c87ce95 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/colorAdjust.glsl @@ -0,0 +1,82 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_brightness; +uniform float u_contrast; +uniform float u_hue; +uniform float u_lightness; +uniform float u_saturation; +out vec4 out_color; + +// RGB to HSL +vec3 rgb2hsl(vec3 c) { + float maxc = max(max(c.r, c.g), c.b); + float minc = min(min(c.r, c.g), c.b); + float l = (maxc + minc) * 0.5; + float s = 0.0; + float h = 0.0; + if (maxc != minc) { + float d = maxc - minc; + s = l > 0.5 ? d / (2.0 - maxc - minc) : d / (maxc + minc); + if (maxc == c.r) { + h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0); + } else if (maxc == c.g) { + h = (c.b - c.r) / d + 2.0; + } else { + h = (c.r - c.g) / d + 4.0; + } + h /= 6.0; + } + return vec3(h, s, l); +} + +// HSL to RGB +float hue2rgb(float p, float q, float t) { + if (t < 0.0) t += 1.0; + if (t > 1.0) t -= 1.0; + if (t < 1.0/6.0) return p + (q - p) * 6.0 * t; + if (t < 1.0/2.0) return q; + if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0; + return p; +} + +vec3 hsl2rgb(vec3 hsl) { + float r, g, b; + float h = hsl.x; + float s = hsl.y; + float l = hsl.z; + if (s == 0.0) { + r = g = b = l; + } else { + float q = l < 0.5 ? l * (1.0 + s) : l + s - l * s; + float p = 2.0 * l - q; + r = hue2rgb(p, q, h + 1.0/3.0); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1.0/3.0); + } + return vec3(r, g, b); +} + +void main() { + vec4 in_color = texture(in_texture, in_uv); + vec3 color = in_color.rgb; + + color = color * u_brightness; + color += vec3(u_lightness); + color = (color - 0.5) * u_contrast + 0.5; + + vec3 hsl = rgb2hsl(color); + hsl.x = mod(hsl.x + u_hue, 1.0); + hsl.y = clamp(hsl.y * u_saturation, 0.0, 1.0); + + color = hsl2rgb(hsl); + out_color = vec4(color, in_color.a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts b/packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts index 8cfbbcb516..989ca79a2c 100644 --- a/packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts +++ b/packages/frontend/src/utility/image-effector/fxs/colorAdjust.ts @@ -4,86 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './colorAdjust.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_brightness; -uniform float u_contrast; -uniform float u_hue; -uniform float u_lightness; -uniform float u_saturation; -out vec4 out_color; - -// RGB to HSL -vec3 rgb2hsl(vec3 c) { - float maxc = max(max(c.r, c.g), c.b); - float minc = min(min(c.r, c.g), c.b); - float l = (maxc + minc) * 0.5; - float s = 0.0; - float h = 0.0; - if (maxc != minc) { - float d = maxc - minc; - s = l > 0.5 ? d / (2.0 - maxc - minc) : d / (maxc + minc); - if (maxc == c.r) { - h = (c.g - c.b) / d + (c.g < c.b ? 6.0 : 0.0); - } else if (maxc == c.g) { - h = (c.b - c.r) / d + 2.0; - } else { - h = (c.r - c.g) / d + 4.0; - } - h /= 6.0; - } - return vec3(h, s, l); -} - -// HSL to RGB -float hue2rgb(float p, float q, float t) { - if (t < 0.0) t += 1.0; - if (t > 1.0) t -= 1.0; - if (t < 1.0/6.0) return p + (q - p) * 6.0 * t; - if (t < 1.0/2.0) return q; - if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0; - return p; -} -vec3 hsl2rgb(vec3 hsl) { - float r, g, b; - float h = hsl.x; - float s = hsl.y; - float l = hsl.z; - if (s == 0.0) { - r = g = b = l; - } else { - float q = l < 0.5 ? l * (1.0 + s) : l + s - l * s; - float p = 2.0 * l - q; - r = hue2rgb(p, q, h + 1.0/3.0); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1.0/3.0); - } - return vec3(r, g, b); -} - -void main() { - vec4 in_color = texture(in_texture, in_uv); - vec3 color = in_color.rgb; - - color = color * u_brightness; - color += vec3(u_lightness); - color = (color - 0.5) * u_contrast + 0.5; - - vec3 hsl = rgb2hsl(color); - hsl.x = mod(hsl.x + u_hue, 1.0); - hsl.y = clamp(hsl.y * u_saturation, 0.0, 1.0); - - color = hsl2rgb(hsl); - out_color = vec4(color, in_color.a); -} -`; - export const FX_colorAdjust = defineImageEffectorFx({ id: 'colorAdjust', name: i18n.ts._imageEffector._fxs.colorAdjust, diff --git a/packages/frontend/src/utility/image-effector/fxs/colorClamp.glsl b/packages/frontend/src/utility/image-effector/fxs/colorClamp.glsl new file mode 100644 index 0000000000..bf37f5ab43 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/colorClamp.glsl @@ -0,0 +1,29 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// colorClamp, colorClampAdvanced共通 +// colorClampではmax, minがすべて同じ値となる + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_rMax; +uniform float u_rMin; +uniform float u_gMax; +uniform float u_gMin; +uniform float u_bMax; +uniform float u_bMin; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + float r = min(max(in_color.r, u_rMin), u_rMax); + float g = min(max(in_color.g, u_gMin), u_gMax); + float b = min(max(in_color.b, u_bMin), u_bMax); + out_color = vec4(r, g, b, in_color.a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/colorClamp.ts b/packages/frontend/src/utility/image-effector/fxs/colorClamp.ts index 4f18eb63c4..f3513011fa 100644 --- a/packages/frontend/src/utility/image-effector/fxs/colorClamp.ts +++ b/packages/frontend/src/utility/image-effector/fxs/colorClamp.ts @@ -4,32 +4,14 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './colorClamp.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_max; -uniform float u_min; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float r = min(max(in_color.r, u_min), u_max); - float g = min(max(in_color.g, u_min), u_max); - float b = min(max(in_color.b, u_min), u_max); - out_color = vec4(r, g, b, in_color.a); -} -`; - export const FX_colorClamp = defineImageEffectorFx({ id: 'colorClamp', name: i18n.ts._imageEffector._fxs.colorClamp, shader, - uniforms: ['max', 'min'] as const, + uniforms: ['rMax', 'rMin', 'gMax', 'gMin', 'bMax', 'bMin'] as const, params: { max: { label: i18n.ts._imageEffector._fxProps.max, @@ -51,7 +33,11 @@ export const FX_colorClamp = defineImageEffectorFx({ }, }, main: ({ gl, u, params }) => { - gl.uniform1f(u.max, params.max); - gl.uniform1f(u.min, 1.0 + params.min); + gl.uniform1f(u.rMax, params.max); + gl.uniform1f(u.rMin, 1.0 + params.min); + gl.uniform1f(u.gMax, params.max); + gl.uniform1f(u.gMin, 1.0 + params.min); + gl.uniform1f(u.bMax, params.max); + gl.uniform1f(u.bMin, 1.0 + params.min); }, }); diff --git a/packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts b/packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts index 7e793061cf..397e16c1ba 100644 --- a/packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts +++ b/packages/frontend/src/utility/image-effector/fxs/colorClampAdvanced.ts @@ -4,31 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './colorClamp.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_rMax; -uniform float u_rMin; -uniform float u_gMax; -uniform float u_gMin; -uniform float u_bMax; -uniform float u_bMin; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float r = min(max(in_color.r, u_rMin), u_rMax); - float g = min(max(in_color.g, u_gMin), u_gMax); - float b = min(max(in_color.b, u_bMin), u_bMax); - out_color = vec4(r, g, b, in_color.a); -} -`; - export const FX_colorClampAdvanced = defineImageEffectorFx({ id: 'colorClampAdvanced', name: i18n.ts._imageEffector._fxs.colorClampAdvanced, diff --git a/packages/frontend/src/utility/image-effector/fxs/distort.glsl b/packages/frontend/src/utility/image-effector/fxs/distort.glsl new file mode 100644 index 0000000000..7e0d1e3252 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/distort.glsl @@ -0,0 +1,30 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_phase; +uniform float u_frequency; +uniform float u_strength; +uniform int u_direction; // 0: vertical, 1: horizontal +out vec4 out_color; + +void main() { + float v = u_direction == 0 ? + sin((HALF_PI + (u_phase * PI) - (u_frequency / 2.0)) + in_uv.y * u_frequency) * u_strength : + sin((HALF_PI + (u_phase * PI) - (u_frequency / 2.0)) + in_uv.x * u_frequency) * u_strength; + vec4 in_color = u_direction == 0 ? + texture(in_texture, vec2(in_uv.x + v, in_uv.y)) : + texture(in_texture, vec2(in_uv.x, in_uv.y + v)); + out_color = in_color; +} diff --git a/packages/frontend/src/utility/image-effector/fxs/distort.ts b/packages/frontend/src/utility/image-effector/fxs/distort.ts index 7b5ec45f4b..3ea93a0266 100644 --- a/packages/frontend/src/utility/image-effector/fxs/distort.ts +++ b/packages/frontend/src/utility/image-effector/fxs/distort.ts @@ -4,35 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './distort.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_phase; -uniform float u_frequency; -uniform float u_strength; -uniform int u_direction; // 0: vertical, 1: horizontal -out vec4 out_color; - -void main() { - float v = u_direction == 0 ? - sin((HALF_PI + (u_phase * PI) - (u_frequency / 2.0)) + in_uv.y * u_frequency) * u_strength : - sin((HALF_PI + (u_phase * PI) - (u_frequency / 2.0)) + in_uv.x * u_frequency) * u_strength; - vec4 in_color = u_direction == 0 ? - texture(in_texture, vec2(in_uv.x + v, in_uv.y)) : - texture(in_texture, vec2(in_uv.x, in_uv.y + v)); - out_color = in_color; -} -`; - export const FX_distort = defineImageEffectorFx({ id: 'distort', name: i18n.ts._imageEffector._fxs.distort, diff --git a/packages/frontend/src/utility/image-effector/fxs/fill.glsl b/packages/frontend/src/utility/image-effector/fxs/fill.glsl new file mode 100644 index 0000000000..f04dc5545a --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/fill.glsl @@ -0,0 +1,50 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform vec2 u_offset; +uniform vec2 u_scale; +uniform bool u_ellipse; +uniform float u_angle; +uniform vec3 u_color; +uniform float u_opacity; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + //float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); + //float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); + + float angle = -(u_angle * PI); + vec2 centeredUv = in_uv - vec2(0.5, 0.5) - u_offset; + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ) + u_offset; + + bool isInside = false; + if (u_ellipse) { + vec2 norm = (rotatedUV - u_offset) / u_scale; + isInside = dot(norm, norm) <= 1.0; + } else { + isInside = rotatedUV.x > u_offset.x - u_scale.x && rotatedUV.x < u_offset.x + u_scale.x && rotatedUV.y > u_offset.y - u_scale.y && rotatedUV.y < u_offset.y + u_scale.y; + } + + out_color = isInside ? vec4( + mix(in_color.r, u_color.r, u_opacity), + mix(in_color.g, u_color.g, u_opacity), + mix(in_color.b, u_color.b, u_opacity), + in_color.a + ) : in_color; +} diff --git a/packages/frontend/src/utility/image-effector/fxs/fill.ts b/packages/frontend/src/utility/image-effector/fxs/fill.ts index 35dee594e3..772cd76cf7 100644 --- a/packages/frontend/src/utility/image-effector/fxs/fill.ts +++ b/packages/frontend/src/utility/image-effector/fxs/fill.ts @@ -4,55 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './fill.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform vec2 u_offset; -uniform vec2 u_scale; -uniform bool u_ellipse; -uniform float u_angle; -uniform vec3 u_color; -uniform float u_opacity; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - //float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); - //float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); - - float angle = -(u_angle * PI); - vec2 centeredUv = in_uv - vec2(0.5, 0.5) - u_offset; - vec2 rotatedUV = vec2( - centeredUv.x * cos(angle) - centeredUv.y * sin(angle), - centeredUv.x * sin(angle) + centeredUv.y * cos(angle) - ) + u_offset; - - bool isInside = false; - if (u_ellipse) { - vec2 norm = (rotatedUV - u_offset) / u_scale; - isInside = dot(norm, norm) <= 1.0; - } else { - isInside = rotatedUV.x > u_offset.x - u_scale.x && rotatedUV.x < u_offset.x + u_scale.x && rotatedUV.y > u_offset.y - u_scale.y && rotatedUV.y < u_offset.y + u_scale.y; - } - - out_color = isInside ? vec4( - mix(in_color.r, u_color.r, u_opacity), - mix(in_color.g, u_color.g, u_opacity), - mix(in_color.b, u_color.b, u_opacity), - in_color.a - ) : in_color; -} -`; - export const FX_fill = defineImageEffectorFx({ id: 'fill', name: i18n.ts._imageEffector._fxs.fill, diff --git a/packages/frontend/src/utility/image-effector/fxs/grayscale.glsl b/packages/frontend/src/utility/image-effector/fxs/grayscale.glsl new file mode 100644 index 0000000000..54ca719976 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/grayscale.glsl @@ -0,0 +1,22 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +out vec4 out_color; + +float getBrightness(vec4 color) { + return (color.r + color.g + color.b) / 3.0; +} + +void main() { + vec4 in_color = texture(in_texture, in_uv); + float brightness = getBrightness(in_color); + out_color = vec4(brightness, brightness, brightness, in_color.a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/grayscale.ts b/packages/frontend/src/utility/image-effector/fxs/grayscale.ts index e1a288fc85..055e8b4618 100644 --- a/packages/frontend/src/utility/image-effector/fxs/grayscale.ts +++ b/packages/frontend/src/utility/image-effector/fxs/grayscale.ts @@ -4,27 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './grayscale.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -out vec4 out_color; - -float getBrightness(vec4 color) { - return (color.r + color.g + color.b) / 3.0; -} - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float brightness = getBrightness(in_color); - out_color = vec4(brightness, brightness, brightness, in_color.a); -} -`; - export const FX_grayscale = defineImageEffectorFx({ id: 'grayscale', name: i18n.ts._imageEffector._fxs.grayscale, diff --git a/packages/frontend/src/utility/image-effector/fxs/invert.glsl b/packages/frontend/src/utility/image-effector/fxs/invert.glsl new file mode 100644 index 0000000000..a2d1574f5b --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/invert.glsl @@ -0,0 +1,23 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform bool u_r; +uniform bool u_g; +uniform bool u_b; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + out_color.r = u_r ? 1.0 - in_color.r : in_color.r; + out_color.g = u_g ? 1.0 - in_color.g : in_color.g; + out_color.b = u_b ? 1.0 - in_color.b : in_color.b; + out_color.a = in_color.a; +} diff --git a/packages/frontend/src/utility/image-effector/fxs/invert.ts b/packages/frontend/src/utility/image-effector/fxs/invert.ts index 1c662ae849..9417047931 100644 --- a/packages/frontend/src/utility/image-effector/fxs/invert.ts +++ b/packages/frontend/src/utility/image-effector/fxs/invert.ts @@ -4,28 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './invert.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform bool u_r; -uniform bool u_g; -uniform bool u_b; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - out_color.r = u_r ? 1.0 - in_color.r : in_color.r; - out_color.g = u_g ? 1.0 - in_color.g : in_color.g; - out_color.b = u_b ? 1.0 - in_color.b : in_color.b; - out_color.a = in_color.a; -} -`; - export const FX_invert = defineImageEffectorFx({ id: 'invert', name: i18n.ts._imageEffector._fxs.invert, diff --git a/packages/frontend/src/utility/image-effector/fxs/mirror.glsl b/packages/frontend/src/utility/image-effector/fxs/mirror.glsl new file mode 100644 index 0000000000..b27934e9ef --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/mirror.glsl @@ -0,0 +1,26 @@ +#version 300 es +precision mediump float; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform int u_h; +uniform int u_v; +out vec4 out_color; + +void main() { + vec2 uv = in_uv; + if (u_h == -1 && in_uv.x > 0.5) { + uv.x = 1.0 - uv.x; + } + if (u_h == 1 && in_uv.x < 0.5) { + uv.x = 1.0 - uv.x; + } + if (u_v == -1 && in_uv.y > 0.5) { + uv.y = 1.0 - uv.y; + } + if (u_v == 1 && in_uv.y < 0.5) { + uv.y = 1.0 - uv.y; + } + out_color = texture(in_texture, uv); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/mirror.ts b/packages/frontend/src/utility/image-effector/fxs/mirror.ts index 3d7893f8b0..6515454ead 100644 --- a/packages/frontend/src/utility/image-effector/fxs/mirror.ts +++ b/packages/frontend/src/utility/image-effector/fxs/mirror.ts @@ -4,36 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './mirror.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform int u_h; -uniform int u_v; -out vec4 out_color; - -void main() { - vec2 uv = in_uv; - if (u_h == -1 && in_uv.x > 0.5) { - uv.x = 1.0 - uv.x; - } - if (u_h == 1 && in_uv.x < 0.5) { - uv.x = 1.0 - uv.x; - } - if (u_v == -1 && in_uv.y > 0.5) { - uv.y = 1.0 - uv.y; - } - if (u_v == 1 && in_uv.y < 0.5) { - uv.y = 1.0 - uv.y; - } - out_color = texture(in_texture, uv); -} -`; - export const FX_mirror = defineImageEffectorFx({ id: 'mirror', name: i18n.ts._imageEffector._fxs.mirror, diff --git a/packages/frontend/src/utility/image-effector/fxs/pixelate.glsl b/packages/frontend/src/utility/image-effector/fxs/pixelate.glsl new file mode 100644 index 0000000000..4de3f27397 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/pixelate.glsl @@ -0,0 +1,68 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform vec2 u_offset; +uniform vec2 u_scale; +uniform bool u_ellipse; +uniform float u_angle; +uniform int u_samples; +uniform float u_strength; +out vec4 out_color; + +// TODO: pixelateの中心を画像中心ではなく範囲の中心にする +// TODO: 画像のアスペクト比に関わらず各画素は正方形にする + +void main() { + if (u_strength <= 0.0) { + out_color = texture(in_texture, in_uv); + return; + } + + float angle = -(u_angle * PI); + vec2 centeredUv = in_uv - vec2(0.5, 0.5) - u_offset; + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ) + u_offset; + + bool isInside = false; + if (u_ellipse) { + vec2 norm = (rotatedUV - u_offset) / u_scale; + isInside = dot(norm, norm) <= 1.0; + } else { + isInside = rotatedUV.x > u_offset.x - u_scale.x && rotatedUV.x < u_offset.x + u_scale.x && rotatedUV.y > u_offset.y - u_scale.y && rotatedUV.y < u_offset.y + u_scale.y; + } + + if (!isInside) { + out_color = texture(in_texture, in_uv); + return; + } + + float dx = u_strength / 1.0; + float dy = u_strength / 1.0; + vec2 new_uv = vec2( + (dx * (floor((in_uv.x - 0.5 - (dx / 2.0)) / dx) + 0.5)), + (dy * (floor((in_uv.y - 0.5 - (dy / 2.0)) / dy) + 0.5)) + ) + vec2(0.5 + (dx / 2.0), 0.5 + (dy / 2.0)); + + vec4 result = vec4(0.0); + float totalSamples = 0.0; + + // TODO: より多くのサンプリング + result += texture(in_texture, new_uv); + totalSamples += 1.0; + + out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/pixelate.ts b/packages/frontend/src/utility/image-effector/fxs/pixelate.ts index d9a5f454f3..e3eef49b23 100644 --- a/packages/frontend/src/utility/image-effector/fxs/pixelate.ts +++ b/packages/frontend/src/utility/image-effector/fxs/pixelate.ts @@ -4,73 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './pixelate.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform vec2 u_offset; -uniform vec2 u_scale; -uniform bool u_ellipse; -uniform float u_angle; -uniform int u_samples; -uniform float u_strength; -out vec4 out_color; - -// TODO: pixelateの中心を画像中心ではなく範囲の中心にする -// TODO: 画像のアスペクト比に関わらず各画素は正方形にする - -void main() { - if (u_strength <= 0.0) { - out_color = texture(in_texture, in_uv); - return; - } - - float angle = -(u_angle * PI); - vec2 centeredUv = in_uv - vec2(0.5, 0.5) - u_offset; - vec2 rotatedUV = vec2( - centeredUv.x * cos(angle) - centeredUv.y * sin(angle), - centeredUv.x * sin(angle) + centeredUv.y * cos(angle) - ) + u_offset; - - bool isInside = false; - if (u_ellipse) { - vec2 norm = (rotatedUV - u_offset) / u_scale; - isInside = dot(norm, norm) <= 1.0; - } else { - isInside = rotatedUV.x > u_offset.x - u_scale.x && rotatedUV.x < u_offset.x + u_scale.x && rotatedUV.y > u_offset.y - u_scale.y && rotatedUV.y < u_offset.y + u_scale.y; - } - - if (!isInside) { - out_color = texture(in_texture, in_uv); - return; - } - - float dx = u_strength / 1.0; - float dy = u_strength / 1.0; - vec2 new_uv = vec2( - (dx * (floor((in_uv.x - 0.5 - (dx / 2.0)) / dx) + 0.5)), - (dy * (floor((in_uv.y - 0.5 - (dy / 2.0)) / dy) + 0.5)) - ) + vec2(0.5 + (dx / 2.0), 0.5 + (dy / 2.0)); - - vec4 result = vec4(0.0); - float totalSamples = 0.0; - - // TODO: より多くのサンプリング - result += texture(in_texture, new_uv); - totalSamples += 1.0; - - out_color = totalSamples > 0.0 ? result / totalSamples : texture(in_texture, in_uv); -} -`; - export const FX_pixelate = defineImageEffectorFx({ id: 'pixelate', name: i18n.ts._imageEffector._fxs.pixelate, diff --git a/packages/frontend/src/utility/image-effector/fxs/polkadot.glsl b/packages/frontend/src/utility/image-effector/fxs/polkadot.glsl new file mode 100644 index 0000000000..39ecad34b5 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/polkadot.glsl @@ -0,0 +1,75 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_angle; +uniform float u_scale; +uniform float u_major_radius; +uniform float u_major_opacity; +uniform float u_minor_divisions; +uniform float u_minor_radius; +uniform float u_minor_opacity; +uniform vec3 u_color; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); + float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); + + float angle = -(u_angle * PI); + vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ); + + float major_modX = mod(rotatedUV.x, (1.0 / u_scale)); + float major_modY = mod(rotatedUV.y, (1.0 / u_scale)); + float major_threshold = ((u_major_radius / 2.0) / u_scale); + if ( + length(vec2(major_modX, major_modY)) < major_threshold || + length(vec2((1.0 / u_scale) - major_modX, major_modY)) < major_threshold || + length(vec2(major_modX, (1.0 / u_scale) - major_modY)) < major_threshold || + length(vec2((1.0 / u_scale) - major_modX, (1.0 / u_scale) - major_modY)) < major_threshold + ) { + out_color = vec4( + mix(in_color.r, u_color.r, u_major_opacity), + mix(in_color.g, u_color.g, u_major_opacity), + mix(in_color.b, u_color.b, u_major_opacity), + in_color.a + ); + return; + } + + float minor_modX = mod(rotatedUV.x, (1.0 / u_scale / u_minor_divisions)); + float minor_modY = mod(rotatedUV.y, (1.0 / u_scale / u_minor_divisions)); + float minor_threshold = ((u_minor_radius / 2.0) / (u_minor_divisions * u_scale)); + if ( + length(vec2(minor_modX, minor_modY)) < minor_threshold || + length(vec2((1.0 / u_scale / u_minor_divisions) - minor_modX, minor_modY)) < minor_threshold || + length(vec2(minor_modX, (1.0 / u_scale / u_minor_divisions) - minor_modY)) < minor_threshold || + length(vec2((1.0 / u_scale / u_minor_divisions) - minor_modX, (1.0 / u_scale / u_minor_divisions) - minor_modY)) < minor_threshold + ) { + out_color = vec4( + mix(in_color.r, u_color.r, u_minor_opacity), + mix(in_color.g, u_color.g, u_minor_opacity), + mix(in_color.b, u_color.b, u_minor_opacity), + in_color.a + ); + return; + } + + out_color = in_color; +} diff --git a/packages/frontend/src/utility/image-effector/fxs/polkadot.ts b/packages/frontend/src/utility/image-effector/fxs/polkadot.ts index 1685601bd2..521e08cc7b 100644 --- a/packages/frontend/src/utility/image-effector/fxs/polkadot.ts +++ b/packages/frontend/src/utility/image-effector/fxs/polkadot.ts @@ -4,80 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './polkadot.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_angle; -uniform float u_scale; -uniform float u_major_radius; -uniform float u_major_opacity; -uniform float u_minor_divisions; -uniform float u_minor_radius; -uniform float u_minor_opacity; -uniform vec3 u_color; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); - float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); - - float angle = -(u_angle * PI); - vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); - vec2 rotatedUV = vec2( - centeredUv.x * cos(angle) - centeredUv.y * sin(angle), - centeredUv.x * sin(angle) + centeredUv.y * cos(angle) - ); - - float major_modX = mod(rotatedUV.x, (1.0 / u_scale)); - float major_modY = mod(rotatedUV.y, (1.0 / u_scale)); - float major_threshold = ((u_major_radius / 2.0) / u_scale); - if ( - length(vec2(major_modX, major_modY)) < major_threshold || - length(vec2((1.0 / u_scale) - major_modX, major_modY)) < major_threshold || - length(vec2(major_modX, (1.0 / u_scale) - major_modY)) < major_threshold || - length(vec2((1.0 / u_scale) - major_modX, (1.0 / u_scale) - major_modY)) < major_threshold - ) { - out_color = vec4( - mix(in_color.r, u_color.r, u_major_opacity), - mix(in_color.g, u_color.g, u_major_opacity), - mix(in_color.b, u_color.b, u_major_opacity), - in_color.a - ); - return; - } - - float minor_modX = mod(rotatedUV.x, (1.0 / u_scale / u_minor_divisions)); - float minor_modY = mod(rotatedUV.y, (1.0 / u_scale / u_minor_divisions)); - float minor_threshold = ((u_minor_radius / 2.0) / (u_minor_divisions * u_scale)); - if ( - length(vec2(minor_modX, minor_modY)) < minor_threshold || - length(vec2((1.0 / u_scale / u_minor_divisions) - minor_modX, minor_modY)) < minor_threshold || - length(vec2(minor_modX, (1.0 / u_scale / u_minor_divisions) - minor_modY)) < minor_threshold || - length(vec2((1.0 / u_scale / u_minor_divisions) - minor_modX, (1.0 / u_scale / u_minor_divisions) - minor_modY)) < minor_threshold - ) { - out_color = vec4( - mix(in_color.r, u_color.r, u_minor_opacity), - mix(in_color.g, u_color.g, u_minor_opacity), - mix(in_color.b, u_color.b, u_minor_opacity), - in_color.a - ); - return; - } - - out_color = in_color; -} -`; - // Primarily used for watermark export const FX_polkadot = defineImageEffectorFx({ id: 'polkadot', diff --git a/packages/frontend/src/utility/image-effector/fxs/stripe.glsl b/packages/frontend/src/utility/image-effector/fxs/stripe.glsl new file mode 100644 index 0000000000..bb18d8fcb8 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/stripe.glsl @@ -0,0 +1,45 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; +const float TWO_PI = 6.283185307179586; +const float HALF_PI = 1.5707963267948966; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_angle; +uniform float u_frequency; +uniform float u_phase; +uniform float u_threshold; +uniform vec3 u_color; +uniform float u_opacity; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); + float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); + + float angle = -(u_angle * PI); + vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); + vec2 rotatedUV = vec2( + centeredUv.x * cos(angle) - centeredUv.y * sin(angle), + centeredUv.x * sin(angle) + centeredUv.y * cos(angle) + ); + + float phase = u_phase * TWO_PI; + float value = (1.0 + sin((rotatedUV.x * u_frequency - HALF_PI) + phase)) / 2.0; + value = value < u_threshold ? 1.0 : 0.0; + out_color = vec4( + mix(in_color.r, u_color.r, value * u_opacity), + mix(in_color.g, u_color.g, value * u_opacity), + mix(in_color.b, u_color.b, value * u_opacity), + in_color.a + ); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/stripe.ts b/packages/frontend/src/utility/image-effector/fxs/stripe.ts index 1c054c1aaa..3a6ecf970c 100644 --- a/packages/frontend/src/utility/image-effector/fxs/stripe.ts +++ b/packages/frontend/src/utility/image-effector/fxs/stripe.ts @@ -4,50 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './stripe.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; -const float TWO_PI = 6.283185307179586; -const float HALF_PI = 1.5707963267948966; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_angle; -uniform float u_frequency; -uniform float u_phase; -uniform float u_threshold; -uniform vec3 u_color; -uniform float u_opacity; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float x_ratio = max(in_resolution.x / in_resolution.y, 1.0); - float y_ratio = max(in_resolution.y / in_resolution.x, 1.0); - - float angle = -(u_angle * PI); - vec2 centeredUv = (in_uv - vec2(0.5, 0.5)) * vec2(x_ratio, y_ratio); - vec2 rotatedUV = vec2( - centeredUv.x * cos(angle) - centeredUv.y * sin(angle), - centeredUv.x * sin(angle) + centeredUv.y * cos(angle) - ); - - float phase = u_phase * TWO_PI; - float value = (1.0 + sin((rotatedUV.x * u_frequency - HALF_PI) + phase)) / 2.0; - value = value < u_threshold ? 1.0 : 0.0; - out_color = vec4( - mix(in_color.r, u_color.r, value * u_opacity), - mix(in_color.g, u_color.g, value * u_opacity), - mix(in_color.b, u_color.b, value * u_opacity), - in_color.a - ); -} -`; - // Primarily used for watermark export const FX_stripe = defineImageEffectorFx({ id: 'stripe', diff --git a/packages/frontend/src/utility/image-effector/fxs/tearing.glsl b/packages/frontend/src/utility/image-effector/fxs/tearing.glsl new file mode 100644 index 0000000000..3fb2fc2cad --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/tearing.glsl @@ -0,0 +1,33 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform int u_amount; +uniform float u_shiftStrengths[128]; +uniform float u_shiftOrigins[128]; +uniform float u_shiftHeights[128]; +uniform float u_channelShift; +out vec4 out_color; + +void main() { + float v = 0.0; + + for (int i = 0; i < u_amount; i++) { + if (in_uv.y > (u_shiftOrigins[i] - u_shiftHeights[i]) && in_uv.y < (u_shiftOrigins[i] + u_shiftHeights[i])) { + v += u_shiftStrengths[i]; + } + } + + float r = texture(in_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r; + float g = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).g; + float b = texture(in_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b; + float a = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).a; + out_color = vec4(r, g, b, a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/tearing.ts b/packages/frontend/src/utility/image-effector/fxs/tearing.ts index a1d5178d24..453b16bb19 100644 --- a/packages/frontend/src/utility/image-effector/fxs/tearing.ts +++ b/packages/frontend/src/utility/image-effector/fxs/tearing.ts @@ -4,39 +4,10 @@ */ import seedrandom from 'seedrandom'; +import shader from './tearing.glsl'; import { defineImageEffectorFx } from '../ImageEffector.js'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform int u_amount; -uniform float u_shiftStrengths[128]; -uniform float u_shiftOrigins[128]; -uniform float u_shiftHeights[128]; -uniform float u_channelShift; -out vec4 out_color; - -void main() { - float v = 0.0; - - for (int i = 0; i < u_amount; i++) { - if (in_uv.y > (u_shiftOrigins[i] - u_shiftHeights[i]) && in_uv.y < (u_shiftOrigins[i] + u_shiftHeights[i])) { - v += u_shiftStrengths[i]; - } - } - - float r = texture(in_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r; - float g = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).g; - float b = texture(in_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b; - float a = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).a; - out_color = vec4(r, g, b, a); -} -`; - export const FX_tearing = defineImageEffectorFx({ id: 'tearing', name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.tearing, diff --git a/packages/frontend/src/utility/image-effector/fxs/threshold.glsl b/packages/frontend/src/utility/image-effector/fxs/threshold.glsl new file mode 100644 index 0000000000..5ca8c46c39 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/threshold.glsl @@ -0,0 +1,23 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform float u_r; +uniform float u_g; +uniform float u_b; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + float r = in_color.r < u_r ? 0.0 : 1.0; + float g = in_color.g < u_g ? 0.0 : 1.0; + float b = in_color.b < u_b ? 0.0 : 1.0; + out_color = vec4(r, g, b, in_color.a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/threshold.ts b/packages/frontend/src/utility/image-effector/fxs/threshold.ts index 3e591fc939..d0bb8305ae 100644 --- a/packages/frontend/src/utility/image-effector/fxs/threshold.ts +++ b/packages/frontend/src/utility/image-effector/fxs/threshold.ts @@ -4,28 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; +import shader from './threshold.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform float u_r; -uniform float u_g; -uniform float u_b; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - float r = in_color.r < u_r ? 0.0 : 1.0; - float g = in_color.g < u_g ? 0.0 : 1.0; - float b = in_color.b < u_b ? 0.0 : 1.0; - out_color = vec4(r, g, b, in_color.a); -} -`; - export const FX_threshold = defineImageEffectorFx({ id: 'threshold', name: i18n.ts._imageEffector._fxs.threshold, diff --git a/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.glsl b/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.glsl new file mode 100644 index 0000000000..d6a1ef1820 --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.glsl @@ -0,0 +1,147 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const float PI = 3.141592653589793; + +in vec2 in_uv; // 0..1 +uniform sampler2D in_texture; // 背景 +uniform vec2 in_resolution; // 出力解像度(px) + +uniform sampler2D u_watermark; // ウォーターマーク +uniform vec2 u_wmResolution; // ウォーターマーク元解像度(px) + +uniform float u_opacity; // 0..1 +uniform float u_scale; // watermarkのスケール +uniform float u_angle; // -1..1 (PI倍) +uniform bool u_cover; // cover基準 or fit基準 +uniform bool u_repeat; // タイル敷き詰め +uniform int u_alignX; // 0:left 1:center 2:right +uniform int u_alignY; // 0:top 1:center 2:bottom +uniform float u_margin; // 余白(比率) +uniform float u_repeatMargin; // 敷き詰め時の余白(比率) +uniform bool u_noBBoxExpansion; // 回転時のBounding Box拡張を抑止 +uniform bool u_wmEnabled; // watermark有効 + +out vec4 out_color; + +mat2 rot(float a) { + float c = cos(a), s = sin(a); + return mat2(c, -s, s, c); +} + +// cover/fitとscaleから、最終的なサイズ(px)を計算 +vec2 computeWmSize(vec2 outSize, vec2 wmSize, bool cover, float scale) { + float wmAspect = wmSize.x / wmSize.y; + float outAspect = outSize.x / outSize.y; + vec2 size; + if (cover) { + if (wmAspect >= outAspect) { + size.y = outSize.y * scale; + size.x = size.y * wmAspect; + } else { + size.x = outSize.x * scale; + size.y = size.x / wmAspect; + } + } else { + if (wmAspect >= outAspect) { + size.x = outSize.x * scale; + size.y = size.x / wmAspect; + } else { + size.y = outSize.y * scale; + size.x = size.y * wmAspect; + } + } + return size; +} + +void main() { + vec2 outSize = in_resolution; + vec2 p = in_uv * outSize; // 出力のピクセル座標 + vec4 base = texture(in_texture, in_uv); + + if (!u_wmEnabled) { + out_color = base; + return; + } + + float theta = u_angle * PI; // ラジアン + vec2 wmSize = computeWmSize(outSize, u_wmResolution, u_cover, u_scale); + vec2 margin = u_repeat ? wmSize * u_repeatMargin : outSize * u_margin; + + // アライメントに基づく回転中心を計算 + float rotateX = 0.0; + float rotateY = 0.0; + if (abs(theta) > 1e-6 && !u_noBBoxExpansion) { + rotateX = abs(abs(wmSize.x * cos(theta)) + abs(wmSize.y * sin(theta)) - wmSize.x) * 0.5; + rotateY = abs(abs(wmSize.x * sin(theta)) + abs(wmSize.y * cos(theta)) - wmSize.y) * 0.5; + } + + float x; + if (u_alignX == 1) { + x = (outSize.x - wmSize.x) * 0.5; + } else if (u_alignX == 0) { + x = rotateX + margin.x; + } else { + x = outSize.x - wmSize.x - margin.x - rotateX; + } + + float y; + if (u_alignY == 1) { + y = (outSize.y - wmSize.y) * 0.5; + } else if (u_alignY == 0) { + y = rotateY + margin.y; + } else { + y = outSize.y - wmSize.y - margin.y - rotateY; + } + + vec2 rectMin = vec2(x, y); + vec2 rectMax = rectMin + wmSize; + vec2 rectCenter = (rectMin + rectMax) * 0.5; + + vec4 wmCol = vec4(0.0); + + if (u_repeat) { + // アライメントに基づく中心で回転 + vec2 q = rectCenter + rot(theta) * (p - rectCenter); + + // タイルグリッドの原点をrectMin(アライメント位置)に設定 + vec2 gridOrigin = rectMin - margin; + vec2 qFromOrigin = q - gridOrigin; + + // タイルサイズ(ウォーターマーク + マージン)で正規化 + vec2 tile = wmSize + margin * 2.0; + vec2 tileUv = qFromOrigin / tile; + + // タイル内のローカル座標(0..1)を取得 + vec2 localUv = fract(tileUv); + + // ローカル座標をピクセル単位に変換 + vec2 localPos = localUv * tile; + + // マージン領域内かチェック + bool inMargin = any(lessThan(localPos, margin)) || any(greaterThanEqual(localPos, margin + wmSize)); + + if (!inMargin) { + // ウォーターマーク領域内: UV座標を計算 + vec2 uvWm = (localPos - margin) / wmSize; + wmCol = texture(u_watermark, uvWm); + } + // マージン領域の場合は透明(wmCol = vec4(0.0))のまま + } else { + // アライメントと回転に従い一枚だけ描画 + vec2 q = rectCenter + rot(theta) * (p - rectCenter); + bool inside = all(greaterThanEqual(q, rectMin)) && all(lessThan(q, rectMax)); + if (inside) { + vec2 uvWm = (q - rectMin) / wmSize; + wmCol = texture(u_watermark, uvWm); + } + } + + float a = clamp(wmCol.a * u_opacity, 0.0, 1.0); + out_color = mix(base, vec4(wmCol.rgb, 1.0), a); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts b/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts index 944e790792..bb51ed796b 100644 --- a/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts +++ b/packages/frontend/src/utility/image-effector/fxs/watermarkPlacement.ts @@ -4,150 +4,7 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; - -const shader = `#version 300 es -precision mediump float; - -const float PI = 3.141592653589793; - -in vec2 in_uv; // 0..1 -uniform sampler2D in_texture; // 背景 -uniform vec2 in_resolution; // 出力解像度(px) - -uniform sampler2D u_watermark; // ウォーターマーク -uniform vec2 u_wmResolution; // ウォーターマーク元解像度(px) - -uniform float u_opacity; // 0..1 -uniform float u_scale; // watermarkのスケール -uniform float u_angle; // -1..1 (PI倍) -uniform bool u_cover; // cover基準 or fit基準 -uniform bool u_repeat; // タイル敷き詰め -uniform int u_alignX; // 0:left 1:center 2:right -uniform int u_alignY; // 0:top 1:center 2:bottom -uniform float u_margin; // 余白(比率) -uniform float u_repeatMargin; // 敷き詰め時の余白(比率) -uniform bool u_noBBoxExpansion; // 回転時のBounding Box拡張を抑止 -uniform bool u_wmEnabled; // watermark有効 - -out vec4 out_color; - -mat2 rot(float a) { - float c = cos(a), s = sin(a); - return mat2(c, -s, s, c); -} - -// cover/fitとscaleから、最終的なサイズ(px)を計算 -vec2 computeWmSize(vec2 outSize, vec2 wmSize, bool cover, float scale) { - float wmAspect = wmSize.x / wmSize.y; - float outAspect = outSize.x / outSize.y; - vec2 size; - if (cover) { - if (wmAspect >= outAspect) { - size.y = outSize.y * scale; - size.x = size.y * wmAspect; - } else { - size.x = outSize.x * scale; - size.y = size.x / wmAspect; - } - } else { - if (wmAspect >= outAspect) { - size.x = outSize.x * scale; - size.y = size.x / wmAspect; - } else { - size.y = outSize.y * scale; - size.x = size.y * wmAspect; - } - } - return size; -} - -void main() { - vec2 outSize = in_resolution; - vec2 p = in_uv * outSize; // 出力のピクセル座標 - vec4 base = texture(in_texture, in_uv); - - if (!u_wmEnabled) { - out_color = base; - return; - } - - float theta = u_angle * PI; // ラジアン - vec2 wmSize = computeWmSize(outSize, u_wmResolution, u_cover, u_scale); - vec2 margin = u_repeat ? wmSize * u_repeatMargin : outSize * u_margin; - - // アライメントに基づく回転中心を計算 - float rotateX = 0.0; - float rotateY = 0.0; - if (abs(theta) > 1e-6 && !u_noBBoxExpansion) { - rotateX = abs(abs(wmSize.x * cos(theta)) + abs(wmSize.y * sin(theta)) - wmSize.x) * 0.5; - rotateY = abs(abs(wmSize.x * sin(theta)) + abs(wmSize.y * cos(theta)) - wmSize.y) * 0.5; - } - - float x; - if (u_alignX == 1) { - x = (outSize.x - wmSize.x) * 0.5; - } else if (u_alignX == 0) { - x = rotateX + margin.x; - } else { - x = outSize.x - wmSize.x - margin.x - rotateX; - } - - float y; - if (u_alignY == 1) { - y = (outSize.y - wmSize.y) * 0.5; - } else if (u_alignY == 0) { - y = rotateY + margin.y; - } else { - y = outSize.y - wmSize.y - margin.y - rotateY; - } - - vec2 rectMin = vec2(x, y); - vec2 rectMax = rectMin + wmSize; - vec2 rectCenter = (rectMin + rectMax) * 0.5; - - vec4 wmCol = vec4(0.0); - - if (u_repeat) { - // アライメントに基づく中心で回転 - vec2 q = rectCenter + rot(theta) * (p - rectCenter); - - // タイルグリッドの原点をrectMin(アライメント位置)に設定 - vec2 gridOrigin = rectMin - margin; - vec2 qFromOrigin = q - gridOrigin; - - // タイルサイズ(ウォーターマーク + マージン)で正規化 - vec2 tile = wmSize + margin * 2.0; - vec2 tileUv = qFromOrigin / tile; - - // タイル内のローカル座標(0..1)を取得 - vec2 localUv = fract(tileUv); - - // ローカル座標をピクセル単位に変換 - vec2 localPos = localUv * tile; - - // マージン領域内かチェック - bool inMargin = any(lessThan(localPos, margin)) || any(greaterThanEqual(localPos, margin + wmSize)); - - if (!inMargin) { - // ウォーターマーク領域内: UV座標を計算 - vec2 uvWm = (localPos - margin) / wmSize; - wmCol = texture(u_watermark, uvWm); - } - // マージン領域の場合は透明(wmCol = vec4(0.0))のまま - } else { - // アライメントと回転に従い一枚だけ描画 - vec2 q = rectCenter + rot(theta) * (p - rectCenter); - bool inside = all(greaterThanEqual(q, rectMin)) && all(lessThan(q, rectMax)); - if (inside) { - vec2 uvWm = (q - rectMin) / wmSize; - wmCol = texture(u_watermark, uvWm); - } - } - - float a = clamp(wmCol.a * u_opacity, 0.0, 1.0); - out_color = mix(base, vec4(wmCol.rgb, 1.0), a); -} -`; +import shader from './watermarkPlacement.glsl'; export const FX_watermarkPlacement = defineImageEffectorFx({ id: 'watermarkPlacement', diff --git a/packages/frontend/src/utility/image-effector/fxs/zoomLines.glsl b/packages/frontend/src/utility/image-effector/fxs/zoomLines.glsl new file mode 100644 index 0000000000..a0f11fcb5b --- /dev/null +++ b/packages/frontend/src/utility/image-effector/fxs/zoomLines.glsl @@ -0,0 +1,48 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// エイリアスを解決してくれないので、プロジェクトルートからの絶対パスにする必要がある +#include /src/shaders/snoise; + +in vec2 in_uv; +uniform sampler2D in_texture; +uniform vec2 in_resolution; +uniform vec2 u_pos; +uniform float u_frequency; +uniform bool u_thresholdEnabled; +uniform float u_threshold; +uniform float u_maskSize; +uniform bool u_black; +out vec4 out_color; + +void main() { + vec4 in_color = texture(in_texture, in_uv); + vec2 centeredUv = (in_uv - vec2(0.5, 0.5)); + vec2 uv = centeredUv; + + float seed = 1.0; + float time = 0.0; + + vec2 noiseUV = (uv - u_pos) / distance((uv - u_pos), vec2(0.0)); + float noiseX = (noiseUV.x + seed) * u_frequency; + float noiseY = (noiseUV.y + seed) * u_frequency; + float noise = (1.0 + snoise(vec3(noiseX, noiseY, time))) / 2.0; + + float t = noise; + if (u_thresholdEnabled) t = t < u_threshold ? 1.0 : 0.0; + + // TODO: マスクの形自体も揺らぎを与える + float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0)); + float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0))); + out_color = vec4( + mix(in_color.r, u_black ? 0.0 : 1.0, t * mask), + mix(in_color.g, u_black ? 0.0 : 1.0, t * mask), + mix(in_color.b, u_black ? 0.0 : 1.0, t * mask), + in_color.a + ); +} diff --git a/packages/frontend/src/utility/image-effector/fxs/zoomLines.ts b/packages/frontend/src/utility/image-effector/fxs/zoomLines.ts index 4ea28658dd..8c0956d24e 100644 --- a/packages/frontend/src/utility/image-effector/fxs/zoomLines.ts +++ b/packages/frontend/src/utility/image-effector/fxs/zoomLines.ts @@ -4,53 +4,9 @@ */ import { defineImageEffectorFx } from '../ImageEffector.js'; -import { GLSL_LIB_SNOISE } from '@/utility/webgl.js'; +import shader from './zoomLines.glsl'; import { i18n } from '@/i18n.js'; -const shader = `#version 300 es -precision mediump float; - -${GLSL_LIB_SNOISE} - -in vec2 in_uv; -uniform sampler2D in_texture; -uniform vec2 in_resolution; -uniform vec2 u_pos; -uniform float u_frequency; -uniform bool u_thresholdEnabled; -uniform float u_threshold; -uniform float u_maskSize; -uniform bool u_black; -out vec4 out_color; - -void main() { - vec4 in_color = texture(in_texture, in_uv); - vec2 centeredUv = (in_uv - vec2(0.5, 0.5)); - vec2 uv = centeredUv; - - float seed = 1.0; - float time = 0.0; - - vec2 noiseUV = (uv - u_pos) / distance((uv - u_pos), vec2(0.0)); - float noiseX = (noiseUV.x + seed) * u_frequency; - float noiseY = (noiseUV.y + seed) * u_frequency; - float noise = (1.0 + snoise(vec3(noiseX, noiseY, time))) / 2.0; - - float t = noise; - if (u_thresholdEnabled) t = t < u_threshold ? 1.0 : 0.0; - - // TODO: マスクの形自体も揺らぎを与える - float d = distance(uv * vec2(2.0, 2.0), u_pos * vec2(2.0, 2.0)); - float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0))); - out_color = vec4( - mix(in_color.r, u_black ? 0.0 : 1.0, t * mask), - mix(in_color.g, u_black ? 0.0 : 1.0, t * mask), - mix(in_color.b, u_black ? 0.0 : 1.0, t * mask), - in_color.a - ); -} -`; - export const FX_zoomLines = defineImageEffectorFx({ id: 'zoomLines', name: i18n.ts._imageEffector._fxs.zoomLines, |