1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-24 13:54:12 +02:00

feat(frontend): 画像編集機能 (#16121)

* wip

* wip

* wip

* wip

* Update watermarker.ts

* wip

* wip

* Update watermarker.ts

* Update MkUploaderDialog.vue

* wip

* Update ImageEffector.ts

* Update ImageEffector.ts

* wip

* wip

* wip

* wip

* wip

* wip

* Update MkRange.vue

* Update MkRange.vue

* wip

* wip

* Update MkImageEffectorDialog.vue

* Update MkImageEffectorDialog.Layer.vue

* wip

* Update zoomLines.ts

* Update zoomLines.ts

* wip

* wip

* Update ImageEffector.ts

* wip

* Update ImageEffector.ts

* wip

* Update ImageEffector.ts

* swip

* wip

* Update ImageEffector.ts

* wop

* Update MkUploaderDialog.vue

* Update ImageEffector.ts

* wip

* wip

* wip

* Update def.ts

* Update def.ts

* test

* test

* Update manager.ts

* Update manager.ts

* Update manager.ts

* Update manager.ts

* Update MkImageEffectorDialog.vue

* wip

* use WEBGL_lose_context

* wip

* Update MkUploaderDialog.vue

* Update drive.vue

* wip

* Update MkUploaderDialog.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip
This commit is contained in:
syuilo
2025-06-03 19:18:29 +09:00
committed by GitHub
parent 9fdc3c5def
commit cd9322a824
33 changed files with 3885 additions and 106 deletions

View File

@@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineImageEffectorFx } from '../ImageEffector.js';
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' as const,
name: i18n.ts._imageEffector._fxs.checker,
shader,
uniforms: ['angle', 'scale', 'color', 'opacity'] as const,
params: {
angle: {
type: 'number' as const,
default: 0,
min: -1.0,
max: 1.0,
step: 0.01,
},
scale: {
type: 'number' as const,
default: 3.0,
min: 1.0,
max: 10.0,
step: 0.1,
},
color: {
type: 'color' as const,
default: [1, 1, 1],
},
opacity: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.angle, params.angle / 2);
gl.uniform1f(u.scale, params.scale * params.scale);
gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]);
gl.uniform1f(u.opacity, params.opacity);
},
});

View File

@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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;
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' as const,
name: i18n.ts._imageEffector._fxs.chromaticAberration,
shader,
uniforms: ['amount', 'start', 'normalize'] as const,
params: {
normalize: {
type: 'boolean' as const,
default: false,
},
amount: {
type: 'number' as const,
default: 0.1,
min: 0.0,
max: 1.0,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.amount, params.amount);
gl.uniform1i(u.normalize, params.normalize ? 1 : 0);
},
});

View File

@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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 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' as const,
name: i18n.ts._imageEffector._fxs.colorClamp,
shader,
uniforms: ['max', 'min'] as const,
params: {
max: {
type: 'number' as const,
default: 1.0,
min: 0.0,
max: 1.0,
step: 0.01,
},
min: {
type: 'number' as const,
default: -1.0,
min: -1.0,
max: 0.0,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.max, params.max);
gl.uniform1f(u.min, 1.0 + params.min);
},
});

View File

@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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 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' as const,
name: i18n.ts._imageEffector._fxs.colorClampAdvanced,
shader,
uniforms: ['rMax', 'rMin', 'gMax', 'gMin', 'bMax', 'bMin'] as const,
params: {
rMax: {
type: 'number' as const,
default: 1.0,
min: 0.0,
max: 1.0,
step: 0.01,
},
rMin: {
type: 'number' as const,
default: -1.0,
min: -1.0,
max: 0.0,
step: 0.01,
},
gMax: {
type: 'number' as const,
default: 1.0,
min: 0.0,
max: 1.0,
step: 0.01,
},
gMin: {
type: 'number' as const,
default: -1.0,
min: -1.0,
max: 0.0,
step: 0.01,
},
bMax: {
type: 'number' as const,
default: 1.0,
min: 0.0,
max: 1.0,
step: 0.01,
},
bMin: {
type: 'number' as const,
default: -1.0,
min: -1.0,
max: 0.0,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.rMax, params.rMax);
gl.uniform1f(u.rMin, 1.0 + params.rMin);
gl.uniform1f(u.gMax, params.gMax);
gl.uniform1f(u.gMin, 1.0 + params.gMin);
gl.uniform1f(u.bMax, params.bMax);
gl.uniform1f(u.bMin, 1.0 + params.bMin);
},
});

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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 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(u_phase + in_uv.y * u_frequency) * u_strength :
sin(u_phase + 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' as const,
name: i18n.ts._imageEffector._fxs.distort,
shader,
uniforms: ['phase', 'frequency', 'strength', 'direction'] as const,
params: {
direction: {
type: 'number:enum' as const,
enum: [{ value: 0, label: 'v' }, { value: 1, label: 'h' }],
default: 0,
},
phase: {
type: 'number' as const,
default: 50.0,
min: 0.0,
max: 100,
step: 0.01,
},
frequency: {
type: 'number' as const,
default: 50,
min: 0,
max: 100,
step: 0.1,
},
strength: {
type: 'number' as const,
default: 0.1,
min: 0,
max: 1,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.phase, params.phase / 10);
gl.uniform1f(u.frequency, params.frequency);
gl.uniform1f(u.strength, params.strength);
gl.uniform1i(u.direction, params.direction);
},
});

View File

@@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import seedrandom from 'seedrandom';
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_glitch = defineImageEffectorFx({
id: 'glitch' as const,
name: i18n.ts._imageEffector._fxs.glitch,
shader,
uniforms: ['amount', 'channelShift'] as const,
params: {
amount: {
type: 'number' as const,
default: 3,
min: 1,
max: 100,
step: 1,
},
strength: {
type: 'number' as const,
default: 5,
min: -100,
max: 100,
step: 0.01,
},
size: {
type: 'number' as const,
default: 20,
min: 0,
max: 100,
step: 0.01,
},
channelShift: {
type: 'number' as const,
default: 0.5,
min: 0,
max: 10,
step: 0.01,
},
seed: {
type: 'seed' as const,
default: 100,
},
},
main: ({ gl, program, u, params }) => {
gl.uniform1i(u.amount, params.amount);
gl.uniform1f(u.channelShift, params.channelShift);
const rnd = seedrandom(params.seed.toString());
for (let i = 0; i < params.amount; i++) {
const o = gl.getUniformLocation(program, `u_shiftOrigins[${i.toString()}]`);
gl.uniform1f(o, rnd());
const s = gl.getUniformLocation(program, `u_shiftStrengths[${i.toString()}]`);
gl.uniform1f(s, (1 - (rnd() * 2)) * (params.strength / 100));
const h = gl.getUniformLocation(program, `u_shiftHeights[${i.toString()}]`);
gl.uniform1f(h, rnd() * (params.size / 100));
}
},
});

View File

@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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;
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' as const,
name: i18n.ts._imageEffector._fxs.grayscale,
shader,
uniforms: [] as const,
params: {
},
main: ({ gl, params }) => {
},
});

View File

@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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 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' as const,
name: i18n.ts._imageEffector._fxs.invert,
shader,
uniforms: ['r', 'g', 'b'] as const,
params: {
r: {
type: 'boolean' as const,
default: true,
},
g: {
type: 'boolean' as const,
default: true,
},
b: {
type: 'boolean' as const,
default: true,
},
},
main: ({ gl, u, params }) => {
gl.uniform1i(u.r, params.r ? 1 : 0);
gl.uniform1i(u.g, params.g ? 1 : 0);
gl.uniform1i(u.b, params.b ? 1 : 0);
},
});

View File

@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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_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' as const,
name: i18n.ts._imageEffector._fxs.mirror,
shader,
uniforms: ['h', 'v'] as const,
params: {
h: {
type: 'number:enum' as const,
enum: [{ value: -1, label: '<-' }, { value: 0, label: '|' }, { value: 1, label: '->' }],
default: -1,
},
v: {
type: 'number:enum' as const,
enum: [{ value: -1, label: '^' }, { value: 0, label: '-' }, { value: 1, label: 'v' }],
default: 0,
},
},
main: ({ gl, u, params }) => {
gl.uniform1i(u.h, params.h);
gl.uniform1i(u.v, params.v);
},
});

View File

@@ -0,0 +1,151 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineImageEffectorFx } from '../ImageEffector.js';
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;
}
`;
export const FX_polkadot = defineImageEffectorFx({
id: 'polkadot' as const,
name: i18n.ts._imageEffector._fxs.polkadot,
shader,
uniforms: ['angle', 'scale', 'major_radius', 'major_opacity', 'minor_divisions', 'minor_radius', 'minor_opacity', 'color'] as const,
params: {
angle: {
type: 'number' as const,
default: 0,
min: -1.0,
max: 1.0,
step: 0.01,
},
scale: {
type: 'number' as const,
default: 3.0,
min: 1.0,
max: 10.0,
step: 0.1,
},
majorRadius: {
type: 'number' as const,
default: 0.1,
min: 0.0,
max: 1.0,
step: 0.01,
},
majorOpacity: {
type: 'number' as const,
default: 0.75,
min: 0.0,
max: 1.0,
step: 0.01,
},
minorDivisions: {
type: 'number' as const,
default: 4,
min: 0,
max: 16,
step: 1,
},
minorRadius: {
type: 'number' as const,
default: 0.25,
min: 0.0,
max: 1.0,
step: 0.01,
},
minorOpacity: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
color: {
type: 'color' as const,
default: [1, 1, 1],
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.angle, params.angle / 2);
gl.uniform1f(u.scale, params.scale * params.scale);
gl.uniform1f(u.major_radius, params.majorRadius);
gl.uniform1f(u.major_opacity, params.majorOpacity);
gl.uniform1f(u.minor_divisions, params.minorDivisions);
gl.uniform1f(u.minor_radius, params.minorRadius);
gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]);
gl.uniform1f(u.minor_opacity, params.minorOpacity);
},
});

View File

@@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineImageEffectorFx } from '../ImageEffector.js';
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
);
}
`;
export const FX_stripe = defineImageEffectorFx({
id: 'stripe' as const,
name: i18n.ts._imageEffector._fxs.stripe,
shader,
uniforms: ['angle', 'frequency', 'phase', 'threshold', 'color', 'opacity'] as const,
params: {
angle: {
type: 'number' as const,
default: 0.5,
min: -1.0,
max: 1.0,
step: 0.01,
},
frequency: {
type: 'number' as const,
default: 10.0,
min: 1.0,
max: 30.0,
step: 0.1,
},
threshold: {
type: 'number' as const,
default: 0.1,
min: 0.0,
max: 1.0,
step: 0.01,
},
color: {
type: 'color' as const,
default: [1, 1, 1],
},
opacity: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.angle, params.angle / 2);
gl.uniform1f(u.frequency, params.frequency * params.frequency);
gl.uniform1f(u.phase, 0.0);
gl.uniform1f(u.threshold, params.threshold);
gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]);
gl.uniform1f(u.opacity, params.opacity);
},
});

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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 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' as const,
name: i18n.ts._imageEffector._fxs.threshold,
shader,
uniforms: ['r', 'g', 'b'] as const,
params: {
r: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
g: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
b: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.r, params.r);
gl.uniform1f(u.g, params.g);
gl.uniform1f(u.b, params.b);
},
});

View File

@@ -0,0 +1,148 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineImageEffectorFx } from '../ImageEffector.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 sampler2D u_texture_watermark;
uniform vec2 u_resolution_watermark;
uniform float u_scale;
uniform float u_angle;
uniform float u_opacity;
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 int u_fitMode; // 0: contain, 1: cover
out vec4 out_color;
void main() {
vec4 in_color = texture(in_texture, in_uv);
float in_x_ratio = max(in_resolution.x / in_resolution.y, 1.0);
float in_y_ratio = max(in_resolution.y / in_resolution.x, 1.0);
bool contain = u_fitMode == 0;
float x_ratio = u_resolution_watermark.x / in_resolution.x;
float y_ratio = u_resolution_watermark.y / in_resolution.y;
float aspect_ratio = contain ?
(min(x_ratio, y_ratio) / max(x_ratio, y_ratio)) :
(max(x_ratio, y_ratio) / min(x_ratio, y_ratio));
float x_scale = contain ?
(x_ratio > y_ratio ? 1.0 * u_scale : aspect_ratio * u_scale) :
(x_ratio > y_ratio ? aspect_ratio * u_scale : 1.0 * u_scale);
float y_scale = contain ?
(y_ratio > x_ratio ? 1.0 * u_scale : aspect_ratio * u_scale) :
(y_ratio > x_ratio ? aspect_ratio * u_scale : 1.0 * u_scale);
float x_offset = u_alignX == 0 ? x_scale / 2.0 : u_alignX == 2 ? 1.0 - (x_scale / 2.0) : 0.5;
float y_offset = u_alignY == 0 ? y_scale / 2.0 : u_alignY == 2 ? 1.0 - (y_scale / 2.0) : 0.5;
float angle = -(u_angle * PI);
vec2 center = vec2(x_offset, y_offset);
//vec2 centeredUv = (in_uv - center) * vec2(in_x_ratio, in_y_ratio);
vec2 centeredUv = (in_uv - center);
vec2 rotatedUV = vec2(
centeredUv.x * cos(angle) - centeredUv.y * sin(angle),
centeredUv.x * sin(angle) + centeredUv.y * cos(angle)
) + center;
// trim
if (!u_repeat) {
bool isInside = rotatedUV.x > x_offset - (x_scale / 2.0) && rotatedUV.x < x_offset + (x_scale / 2.0) &&
rotatedUV.y > y_offset - (y_scale / 2.0) && rotatedUV.y < y_offset + (y_scale / 2.0);
if (!isInside) {
out_color = in_color;
return;
}
}
vec4 watermark_color = texture(u_texture_watermark, vec2(
(rotatedUV.x - (x_offset - (x_scale / 2.0))) / x_scale,
(rotatedUV.y - (y_offset - (y_scale / 2.0))) / y_scale
));
out_color.r = mix(in_color.r, watermark_color.r, u_opacity * watermark_color.a);
out_color.g = mix(in_color.g, watermark_color.g, u_opacity * watermark_color.a);
out_color.b = mix(in_color.b, watermark_color.b, u_opacity * watermark_color.a);
out_color.a = in_color.a * (1.0 - u_opacity * watermark_color.a) + watermark_color.a * u_opacity;
}
`;
export const FX_watermarkPlacement = defineImageEffectorFx({
id: 'watermarkPlacement' as const,
name: '(internal)',
shader,
uniforms: ['texture_watermark', 'resolution_watermark', 'scale', 'angle', 'opacity', 'repeat', 'alignX', 'alignY', 'fitMode'] as const,
params: {
cover: {
type: 'boolean' as const,
default: false,
},
repeat: {
type: 'boolean' as const,
default: false,
},
scale: {
type: 'number' as const,
default: 0.3,
min: 0.0,
max: 1.0,
step: 0.01,
},
angle: {
type: 'number' as const,
default: 0,
min: -1.0,
max: 1.0,
step: 0.01,
},
align: {
type: 'align' as const,
default: { x: 'right', y: 'bottom' },
},
opacity: {
type: 'number' as const,
default: 0.75,
min: 0.0,
max: 1.0,
step: 0.01,
},
watermark: {
type: 'texture' as const,
default: null,
},
},
main: ({ gl, u, params, textures }) => {
if (textures.watermark == null) {
return;
}
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, textures.watermark.texture);
gl.uniform1i(u.texture_watermark, 1);
gl.uniform2fv(u.resolution_watermark, [textures.watermark.width, textures.watermark.height]);
gl.uniform1f(u.scale, params.scale);
gl.uniform1f(u.opacity, params.opacity);
gl.uniform1f(u.angle, params.angle);
gl.uniform1i(u.repeat, params.repeat ? 1 : 0);
gl.uniform1i(u.alignX, params.align.x === 'left' ? 0 : params.align.x === 'right' ? 2 : 1);
gl.uniform1i(u.alignY, params.align.y === 'top' ? 0 : params.align.y === 'bottom' ? 2 : 1);
gl.uniform1i(u.fitMode, params.cover ? 1 : 0);
},
});

View File

@@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
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 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);
float angle = atan(-u_pos.y + (in_uv.y), -u_pos.x + (in_uv.x));
float t = (1.0 + sin(angle * u_frequency)) / 2.0;
if (u_thresholdEnabled) t = t < u_threshold ? 1.0 : 0.0;
float d = distance(in_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' as const,
name: i18n.ts._imageEffector._fxs.zoomLines,
shader,
uniforms: ['pos', 'frequency', 'thresholdEnabled', 'threshold', 'maskSize', 'black'] as const,
params: {
x: {
type: 'number' as const,
default: 0.0,
min: -1.0,
max: 1.0,
step: 0.01,
},
y: {
type: 'number' as const,
default: 0.0,
min: -1.0,
max: 1.0,
step: 0.01,
},
frequency: {
type: 'number' as const,
default: 30.0,
min: 1.0,
max: 200.0,
step: 0.1,
},
thresholdEnabled: {
type: 'boolean' as const,
default: true,
},
threshold: {
type: 'number' as const,
default: 0.2,
min: 0.0,
max: 1.0,
step: 0.01,
},
maskSize: {
type: 'number' as const,
default: 0.5,
min: 0.0,
max: 1.0,
step: 0.01,
},
black: {
type: 'boolean' as const,
default: false,
},
},
main: ({ gl, u, params }) => {
gl.uniform2f(u.pos, (1.0 + params.x) / 2.0, (1.0 + params.y) / 2.0);
gl.uniform1f(u.frequency, params.frequency);
gl.uniform1i(u.thresholdEnabled, params.thresholdEnabled ? 1 : 0);
gl.uniform1f(u.threshold, params.threshold);
gl.uniform1f(u.maskSize, params.maskSize);
gl.uniform1i(u.black, params.black ? 1 : 0);
},
});