mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-20 17:35:31 +02:00
feat(frontend): EXIFフレーム機能 (#16725)
* wip
* wip
* Update ImageEffector.ts
* Update image-label-renderer.ts
* Update image-label-renderer.ts
* wip
* Update image-label-renderer.ts
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update use-uploader.ts
* Update watermark.ts
* wip
* wu
* wip
* Update image-frame-renderer.ts
* wip
* wip
* Update image-frame-renderer.ts
* Create ImageCompositor.ts
* Update ImageCompositor.ts
* wip
* wip
* Update ImageEffector.ts
* wip
* Update use-uploader.ts
* wip
* wip
* wip
* wip
* Update fxs.ts
* wip
* wip
* wip
* Update CHANGELOG.md
* wip
* wip
* Update MkImageEffectorDialog.vue
* Update MkImageEffectorDialog.vue
* Update MkImageFrameEditorDialog.vue
* Update use-uploader.ts
* improve error handling
* Update use-uploader.ts
* 🎨
* wip
* wip
* lazy load
* lazy load
* wip
* wip
* wip
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import seedrandom from 'seedrandom';
|
||||
import shader from './blockNoise.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
amount: number;
|
||||
strength: number;
|
||||
width: number;
|
||||
height: number;
|
||||
channelShift: number;
|
||||
seed: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, program, u, params }) => {
|
||||
gl.uniform1i(u.amount, params.amount);
|
||||
gl.uniform1f(u.channelShift, params.channelShift);
|
||||
|
||||
const margin = 0;
|
||||
|
||||
const rnd = seedrandom(params.seed.toString());
|
||||
|
||||
for (let i = 0; i < params.amount; i++) {
|
||||
const o = gl.getUniformLocation(program, `u_shiftOrigins[${i.toString()}]`);
|
||||
gl.uniform2f(o, (rnd() * (1 + (margin * 2))) - margin, (rnd() * (1 + (margin * 2))) - margin);
|
||||
|
||||
const s = gl.getUniformLocation(program, `u_shiftStrengths[${i.toString()}]`);
|
||||
gl.uniform1f(s, (1 - (rnd() * 2)) * params.strength);
|
||||
|
||||
const sizes = gl.getUniformLocation(program, `u_shiftSizes[${i.toString()}]`);
|
||||
gl.uniform2f(sizes, params.width, params.height);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.blockNoise,
|
||||
params: {
|
||||
amount: {
|
||||
label: i18n.ts._imageEffector._fxProps.amount,
|
||||
type: 'number',
|
||||
default: 50,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
strength: {
|
||||
label: i18n.ts._imageEffector._fxProps.strength,
|
||||
type: 'number',
|
||||
default: 0.05,
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
width: {
|
||||
label: i18n.ts.width,
|
||||
type: 'number',
|
||||
default: 0.05,
|
||||
min: 0.01,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
height: {
|
||||
label: i18n.ts.height,
|
||||
type: 'number',
|
||||
default: 0.01,
|
||||
min: 0.01,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
channelShift: {
|
||||
label: i18n.ts._imageEffector._fxProps.glitchChannelShift,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
seed: {
|
||||
label: i18n.ts._imageEffector._fxProps.seed,
|
||||
type: 'seed',
|
||||
default: 100,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './blur.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
ellipse: boolean;
|
||||
angle: number;
|
||||
radius: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2);
|
||||
gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2);
|
||||
gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0);
|
||||
gl.uniform1f(u.angle, params.angle / 2);
|
||||
gl.uniform1f(u.radius, params.radius);
|
||||
gl.uniform1i(u.samples, 256);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.blur,
|
||||
params: {
|
||||
offsetX: {
|
||||
label: i18n.ts._imageEffector._fxProps.offset + ' X',
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
offsetY: {
|
||||
label: i18n.ts._imageEffector._fxProps.offset + ' Y',
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
scaleX: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale + ' W',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
scaleY: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale + ' H',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
ellipse: {
|
||||
label: i18n.ts._imageEffector._fxProps.circle,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
angle: {
|
||||
label: i18n.ts._imageEffector._fxProps.angle,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 90) + '°',
|
||||
},
|
||||
radius: {
|
||||
label: i18n.ts._imageEffector._fxProps.strength,
|
||||
type: 'number',
|
||||
default: 3.0,
|
||||
min: 0.0,
|
||||
max: 10.0,
|
||||
step: 0.5,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './checker.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
angle: number;
|
||||
scale: number;
|
||||
color: [number, number, number];
|
||||
opacity: number;
|
||||
}>({
|
||||
shader,
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.checker,
|
||||
params: {
|
||||
angle: {
|
||||
label: i18n.ts._imageEffector._fxProps.angle,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 90) + '°',
|
||||
},
|
||||
scale: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale,
|
||||
type: 'number',
|
||||
default: 3.0,
|
||||
min: 1.0,
|
||||
max: 10.0,
|
||||
step: 0.1,
|
||||
},
|
||||
color: {
|
||||
label: i18n.ts._imageEffector._fxProps.color,
|
||||
type: 'color',
|
||||
default: [1, 1, 1],
|
||||
},
|
||||
opacity: {
|
||||
label: i18n.ts._imageEffector._fxProps.opacity,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './chromaticAberration.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
normalize: boolean;
|
||||
amount: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform1f(u.amount, params.amount);
|
||||
gl.uniform1i(u.normalize, params.normalize ? 1 : 0);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.chromaticAberration,
|
||||
params: {
|
||||
normalize: {
|
||||
label: i18n.ts._imageEffector._fxProps.normalize,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
amount: {
|
||||
label: i18n.ts._imageEffector._fxProps.amount,
|
||||
type: 'number',
|
||||
default: 0.1,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './colorAdjust.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
lightness: number;
|
||||
contrast: number;
|
||||
hue: number;
|
||||
brightness: number;
|
||||
saturation: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform1f(u.brightness, params.brightness);
|
||||
gl.uniform1f(u.contrast, params.contrast);
|
||||
gl.uniform1f(u.hue, params.hue / 2);
|
||||
gl.uniform1f(u.lightness, params.lightness);
|
||||
gl.uniform1f(u.saturation, params.saturation);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.colorAdjust,
|
||||
params: {
|
||||
lightness: {
|
||||
label: i18n.ts._imageEffector._fxProps.lightness,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
contrast: {
|
||||
label: i18n.ts._imageEffector._fxProps.contrast,
|
||||
type: 'number',
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
hue: {
|
||||
label: i18n.ts._imageEffector._fxProps.hue,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 180) + '°',
|
||||
},
|
||||
brightness: {
|
||||
label: i18n.ts._imageEffector._fxProps.brightness,
|
||||
type: 'number',
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
saturation: {
|
||||
label: i18n.ts._imageEffector._fxProps.saturation,
|
||||
type: 'number',
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './colorClamp.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
max: number;
|
||||
min: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.colorClamp,
|
||||
params: {
|
||||
max: {
|
||||
label: i18n.ts._imageEffector._fxProps.max,
|
||||
type: 'number',
|
||||
default: 1.0,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
min: {
|
||||
label: i18n.ts._imageEffector._fxProps.min,
|
||||
type: 'number',
|
||||
default: -1.0,
|
||||
min: -1.0,
|
||||
max: 0.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './colorClamp.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
rMax: number;
|
||||
rMin: number;
|
||||
gMax: number;
|
||||
gMin: number;
|
||||
bMax: number;
|
||||
bMin: number;
|
||||
}>({
|
||||
shader,
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.colorClampAdvanced,
|
||||
params: {
|
||||
rMax: {
|
||||
label: `${i18n.ts._imageEffector._fxProps.max} (${i18n.ts._imageEffector._fxProps.redComponent})`,
|
||||
type: 'number',
|
||||
default: 1.0,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
rMin: {
|
||||
label: `${i18n.ts._imageEffector._fxProps.min} (${i18n.ts._imageEffector._fxProps.redComponent})`,
|
||||
type: 'number',
|
||||
default: -1.0,
|
||||
min: -1.0,
|
||||
max: 0.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
gMax: {
|
||||
label: `${i18n.ts._imageEffector._fxProps.max} (${i18n.ts._imageEffector._fxProps.greenComponent})`,
|
||||
type: 'number',
|
||||
default: 1.0,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
gMin: {
|
||||
label: `${i18n.ts._imageEffector._fxProps.min} (${i18n.ts._imageEffector._fxProps.greenComponent})`,
|
||||
type: 'number',
|
||||
default: -1.0,
|
||||
min: -1.0,
|
||||
max: 0.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
bMax: {
|
||||
label: `${i18n.ts._imageEffector._fxProps.max} (${i18n.ts._imageEffector._fxProps.blueComponent})`,
|
||||
type: 'number',
|
||||
default: 1.0,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
bMin: {
|
||||
label: `${i18n.ts._imageEffector._fxProps.min} (${i18n.ts._imageEffector._fxProps.blueComponent})`,
|
||||
type: 'number',
|
||||
default: -1.0,
|
||||
min: -1.0,
|
||||
max: 0.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './distort.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
direction: number;
|
||||
phase: number;
|
||||
frequency: number;
|
||||
strength: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform1f(u.phase, params.phase);
|
||||
gl.uniform1f(u.frequency, params.frequency);
|
||||
gl.uniform1f(u.strength, params.strength);
|
||||
gl.uniform1i(u.direction, params.direction);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.distort,
|
||||
params: {
|
||||
direction: {
|
||||
label: i18n.ts._imageEffector._fxProps.direction,
|
||||
type: 'number:enum',
|
||||
enum: [
|
||||
{ value: 0 as const, label: i18n.ts.horizontal },
|
||||
{ value: 1 as const, label: i18n.ts.vertical },
|
||||
],
|
||||
default: 1,
|
||||
},
|
||||
phase: {
|
||||
label: i18n.ts._imageEffector._fxProps.phase,
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
frequency: {
|
||||
label: i18n.ts._imageEffector._fxProps.frequency,
|
||||
type: 'number',
|
||||
default: 30,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 0.1,
|
||||
},
|
||||
strength: {
|
||||
label: i18n.ts._imageEffector._fxProps.strength,
|
||||
type: 'number',
|
||||
default: 0.05,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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;
|
||||
}
|
||||
100
packages/frontend/src/utility/image-compositor-functions/fill.ts
Normal file
100
packages/frontend/src/utility/image-compositor-functions/fill.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './fill.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
ellipse: boolean;
|
||||
angle: number;
|
||||
color: [number, number, number];
|
||||
opacity: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2);
|
||||
gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2);
|
||||
gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0);
|
||||
gl.uniform1f(u.angle, params.angle / 2);
|
||||
gl.uniform3f(u.color, params.color[0], params.color[1], params.color[2]);
|
||||
gl.uniform1f(u.opacity, params.opacity);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.fill,
|
||||
params: {
|
||||
offsetX: {
|
||||
label: i18n.ts._imageEffector._fxProps.offset + ' X',
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
offsetY: {
|
||||
label: i18n.ts._imageEffector._fxProps.offset + ' Y',
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
scaleX: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale + ' W',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
scaleY: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale + ' H',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
ellipse: {
|
||||
label: i18n.ts._imageEffector._fxProps.circle,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
angle: {
|
||||
label: i18n.ts._imageEffector._fxProps.angle,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 90) + '°',
|
||||
},
|
||||
color: {
|
||||
label: i18n.ts._imageEffector._fxProps.color,
|
||||
type: 'color',
|
||||
default: [1, 1, 1],
|
||||
},
|
||||
opacity: {
|
||||
label: i18n.ts._imageEffector._fxProps.opacity,
|
||||
type: 'number',
|
||||
default: 1.0,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './grayscale.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.grayscale,
|
||||
params: {
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './invert.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
r: boolean;
|
||||
g: boolean;
|
||||
b: boolean;
|
||||
}>({
|
||||
shader,
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.invert,
|
||||
params: {
|
||||
r: {
|
||||
label: i18n.ts._imageEffector._fxProps.redComponent,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
g: {
|
||||
label: i18n.ts._imageEffector._fxProps.greenComponent,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
b: {
|
||||
label: i18n.ts._imageEffector._fxProps.blueComponent,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './mirror.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
h: number;
|
||||
v: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform1i(u.h, params.h);
|
||||
gl.uniform1i(u.v, params.v);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.mirror,
|
||||
params: {
|
||||
h: {
|
||||
label: i18n.ts.horizontal,
|
||||
type: 'number:enum',
|
||||
enum: [
|
||||
{ value: -1 as const, icon: 'ti ti-arrow-bar-right' },
|
||||
{ value: 0 as const, icon: 'ti ti-minus-vertical' },
|
||||
{ value: 1 as const, icon: 'ti ti-arrow-bar-left' },
|
||||
],
|
||||
default: -1,
|
||||
},
|
||||
v: {
|
||||
label: i18n.ts.vertical,
|
||||
type: 'number:enum',
|
||||
enum: [
|
||||
{ value: -1 as const, icon: 'ti ti-arrow-bar-down' },
|
||||
{ value: 0 as const, icon: 'ti ti-minus' },
|
||||
{ value: 1 as const, icon: 'ti ti-arrow-bar-up' },
|
||||
],
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './pixelate.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
ellipse: boolean;
|
||||
angle: number;
|
||||
strength: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform2f(u.offset, params.offsetX / 2, params.offsetY / 2);
|
||||
gl.uniform2f(u.scale, params.scaleX / 2, params.scaleY / 2);
|
||||
gl.uniform1i(u.ellipse, params.ellipse ? 1 : 0);
|
||||
gl.uniform1f(u.angle, params.angle / 2);
|
||||
gl.uniform1f(u.strength, params.strength * params.strength);
|
||||
gl.uniform1i(u.samples, 256);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.pixelate,
|
||||
params: {
|
||||
offsetX: {
|
||||
label: i18n.ts._imageEffector._fxProps.offset + ' X',
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
offsetY: {
|
||||
label: i18n.ts._imageEffector._fxProps.offset + ' Y',
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
scaleX: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale + ' W',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
scaleY: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale + ' H',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
ellipse: {
|
||||
label: i18n.ts._imageEffector._fxProps.circle,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
angle: {
|
||||
label: i18n.ts._imageEffector._fxProps.angle,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 90) + '°',
|
||||
},
|
||||
strength: {
|
||||
label: i18n.ts._imageEffector._fxProps.strength,
|
||||
type: 'number',
|
||||
default: 0.2,
|
||||
min: 0.0,
|
||||
max: 0.5,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './polkadot.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
angle: number;
|
||||
scale: number;
|
||||
majorRadius: number;
|
||||
majorOpacity: number;
|
||||
minorDivisions: number;
|
||||
minorRadius: number;
|
||||
minorOpacity: number;
|
||||
color: [number, number, number];
|
||||
}>({
|
||||
shader,
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.polkadot,
|
||||
params: {
|
||||
angle: {
|
||||
label: i18n.ts._imageEffector._fxProps.angle,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 90) + '°',
|
||||
},
|
||||
scale: {
|
||||
label: i18n.ts._imageEffector._fxProps.scale,
|
||||
type: 'number',
|
||||
default: 3.0,
|
||||
min: 1.0,
|
||||
max: 10.0,
|
||||
step: 0.1,
|
||||
},
|
||||
majorRadius: {
|
||||
label: i18n.ts._watermarkEditor.polkadotMainDotRadius,
|
||||
type: 'number',
|
||||
default: 0.1,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
majorOpacity: {
|
||||
label: i18n.ts._watermarkEditor.polkadotMainDotOpacity,
|
||||
type: 'number',
|
||||
default: 0.75,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
minorDivisions: {
|
||||
label: i18n.ts._watermarkEditor.polkadotSubDotDivisions,
|
||||
type: 'number',
|
||||
default: 4,
|
||||
min: 0,
|
||||
max: 16,
|
||||
step: 1,
|
||||
},
|
||||
minorRadius: {
|
||||
label: i18n.ts._watermarkEditor.polkadotSubDotRadius,
|
||||
type: 'number',
|
||||
default: 0.25,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
minorOpacity: {
|
||||
label: i18n.ts._watermarkEditor.polkadotSubDotOpacity,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
color: {
|
||||
label: i18n.ts._imageEffector._fxProps.color,
|
||||
type: 'color',
|
||||
default: [1, 1, 1],
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './stripe.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
angle: number;
|
||||
frequency: number;
|
||||
threshold: number;
|
||||
color: [number, number, number];
|
||||
opacity: number;
|
||||
}>({
|
||||
shader,
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.stripe,
|
||||
params: {
|
||||
angle: {
|
||||
label: i18n.ts._imageEffector._fxProps.angle,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 90) + '°',
|
||||
},
|
||||
frequency: {
|
||||
label: i18n.ts._watermarkEditor.stripeFrequency,
|
||||
type: 'number',
|
||||
default: 10.0,
|
||||
min: 1.0,
|
||||
max: 30.0,
|
||||
step: 0.1,
|
||||
},
|
||||
threshold: {
|
||||
label: i18n.ts._watermarkEditor.stripeWidth,
|
||||
type: 'number',
|
||||
default: 0.1,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
color: {
|
||||
label: i18n.ts._imageEffector._fxProps.color,
|
||||
type: 'color',
|
||||
default: [1, 1, 1],
|
||||
},
|
||||
opacity: {
|
||||
label: i18n.ts._imageEffector._fxProps.opacity,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import seedrandom from 'seedrandom';
|
||||
import shader from './tearing.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
amount: number;
|
||||
strength: number;
|
||||
size: number;
|
||||
channelShift: number;
|
||||
seed: number;
|
||||
}>({
|
||||
shader,
|
||||
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);
|
||||
|
||||
const h = gl.getUniformLocation(program, `u_shiftHeights[${i.toString()}]`);
|
||||
gl.uniform1f(h, rnd() * params.size);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.tearing,
|
||||
params: {
|
||||
amount: {
|
||||
label: i18n.ts._imageEffector._fxProps.amount,
|
||||
type: 'number',
|
||||
default: 3,
|
||||
min: 1,
|
||||
max: 100,
|
||||
step: 1,
|
||||
},
|
||||
strength: {
|
||||
label: i18n.ts._imageEffector._fxProps.strength,
|
||||
type: 'number',
|
||||
default: 0.05,
|
||||
min: -1,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
size: {
|
||||
label: i18n.ts._imageEffector._fxProps.size,
|
||||
type: 'number',
|
||||
default: 0.2,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
channelShift: {
|
||||
label: i18n.ts._imageEffector._fxProps.glitchChannelShift,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 0.01,
|
||||
toViewValue: v => Math.round(v * 100) + '%',
|
||||
},
|
||||
seed: {
|
||||
label: i18n.ts._imageEffector._fxProps.seed,
|
||||
type: 'seed',
|
||||
default: 100,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './threshold.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform1f(u.r, params.r);
|
||||
gl.uniform1f(u.g, params.g);
|
||||
gl.uniform1f(u.b, params.b);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.threshold,
|
||||
params: {
|
||||
r: {
|
||||
label: i18n.ts._imageEffector._fxProps.redComponent,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
g: {
|
||||
label: i18n.ts._imageEffector._fxProps.greenComponent,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
b: {
|
||||
label: i18n.ts._imageEffector._fxProps.blueComponent,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import shader from './zoomLines.glsl';
|
||||
import type { ImageEffectorUiDefinition } from '../image-effector/ImageEffector.js';
|
||||
import { defineImageCompositorFunction } from '@/lib/ImageCompositor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
export const fn = defineImageCompositorFunction<{
|
||||
x: number;
|
||||
y: number;
|
||||
frequency: number;
|
||||
smoothing: boolean;
|
||||
threshold: number;
|
||||
maskSize: number;
|
||||
black: boolean;
|
||||
}>({
|
||||
shader,
|
||||
main: ({ gl, u, params }) => {
|
||||
gl.uniform2f(u.pos, params.x / 2, params.y / 2);
|
||||
gl.uniform1f(u.frequency, params.frequency * params.frequency);
|
||||
// thresholdの調整が有効な間はsmoothingが利用できない
|
||||
gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1);
|
||||
gl.uniform1f(u.threshold, params.threshold);
|
||||
gl.uniform1f(u.maskSize, params.maskSize);
|
||||
gl.uniform1i(u.black, params.black ? 1 : 0);
|
||||
},
|
||||
});
|
||||
|
||||
export const uiDefinition = {
|
||||
name: i18n.ts._imageEffector._fxs.zoomLines,
|
||||
params: {
|
||||
x: {
|
||||
label: i18n.ts._imageEffector._fxProps.centerX,
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
y: {
|
||||
label: i18n.ts._imageEffector._fxProps.centerY,
|
||||
type: 'number',
|
||||
default: 0.0,
|
||||
min: -1.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
frequency: {
|
||||
label: i18n.ts._imageEffector._fxProps.frequency,
|
||||
type: 'number',
|
||||
default: 5.0,
|
||||
min: 0.0,
|
||||
max: 15.0,
|
||||
step: 0.1,
|
||||
},
|
||||
smoothing: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesSmoothing,
|
||||
caption: i18n.ts._imageEffector._fxProps.zoomLinesSmoothingDescription,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
threshold: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesThreshold,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
maskSize: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesMaskSize,
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
},
|
||||
black: {
|
||||
label: i18n.ts._imageEffector._fxProps.zoomLinesBlack,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
} satisfies ImageEffectorUiDefinition<typeof fn>;
|
||||
Reference in New Issue
Block a user