This commit is contained in:
syuilo 2025-05-28 17:47:17 +09:00
parent bd8d0d78bf
commit 9fb0f7357a
5 changed files with 139 additions and 22 deletions

View File

@ -4,16 +4,28 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="$style.root" class="_gaps"> <MkFolder :defaultOpen="true">
<div v-for="[k, v] in Object.entries(fx.params)" :key="k"> <template #label>{{ fx.id }}</template>
<MkSwitch v-if="v.type === 'boolean'" v-model="layer.params[k]"> <template #footer>
<template #label>{{ k }}</template> <MkButton @click="emit('del')">{{ i18n.ts.remove }}</MkButton>
</MkSwitch> </template>
<MkRange v-else-if="v.type === 'number'" v-model="layer.params[k]" continuousUpdate :min="v.min" :max="v.max" :step="v.step">
<template #label>{{ k }}</template> <div :class="$style.root" class="_gaps">
</MkRange> <div v-for="[k, v] in Object.entries(fx.params)" :key="k">
<MkSwitch v-if="v.type === 'boolean'" v-model="layer.params[k]">
<template #label>{{ k }}</template>
</MkSwitch>
<MkRange v-else-if="v.type === 'number'" v-model="layer.params[k]" continuousUpdate :min="v.min" :max="v.max" :step="v.step">
<template #label>{{ k }}</template>
</MkRange>
<div v-else-if="v.type === 'seed'">
<MkRange v-model="layer.params[k]" continuousUpdate type="number" :min="0" :max="10000" :step="1">
<template #label>{{ k }}</template>
</MkRange>
</div>
</div>
</div> </div>
</div> </MkFolder>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -22,7 +34,7 @@ import { v4 as uuid } from 'uuid';
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js'; import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { FXS, ImageEffector } from '@/utility/image-effector/ImageEffector.js'; import { FXS, ImageEffector } from '@/utility/image-effector/ImageEffector.js';
import MkSelect from '@/components/MkSelect.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
@ -40,6 +52,9 @@ if (fx == null) {
throw new Error(`Unrecognized effect: ${layer.value.fxId}`); throw new Error(`Unrecognized effect: ${layer.value.fxId}`);
} }
const emit = defineEmits<{
(e: 'del'): void;
}>();
</script> </script>
<style module> <style module>

View File

@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-for="(layer, i) in layers" v-for="(layer, i) in layers"
:key="layer.id" :key="layer.id"
v-model:layer="layers[i]" v-model:layer="layers[i]"
@del="onLayerDelete(layer)"
></XLayer> ></XLayer>
<MkButton rounded primary @click="addEffect"><i class="ti ti-plus"></i></MkButton> <MkButton rounded primary @click="addEffect"><i class="ti ti-plus"></i></MkButton>
@ -81,7 +82,7 @@ watch(layers, async () => {
}, { deep: true }); }, { deep: true });
function addEffect(ev: MouseEvent) { function addEffect(ev: MouseEvent) {
os.popupMenu(FXS.map((fx) => ({ os.popupMenu(FXS.filter(fx => fx.id !== 'watermarkPlacement').map((fx) => ({
text: fx.id, text: fx.id,
action: () => { action: () => {
layers.push({ layers.push({
@ -93,6 +94,13 @@ function addEffect(ev: MouseEvent) {
})), ev.currentTarget ?? ev.target); })), ev.currentTarget ?? ev.target);
} }
function onLayerDelete(layer: ImageEffectorLayer) {
const index = layers.indexOf(layer);
if (index !== -1) {
layers.splice(index, 1);
}
}
const canvasEl = useTemplateRef('canvasEl'); const canvasEl = useTemplateRef('canvasEl');
let renderer: ImageEffector | null = null; let renderer: ImageEffector | null = null;

View File

@ -5,12 +5,14 @@
import { getProxiedImageUrl } from '../media-proxy.js'; import { getProxiedImageUrl } from '../media-proxy.js';
import { FX_chromaticAberration } from './fxs/chromaticAberration.js'; import { FX_chromaticAberration } from './fxs/chromaticAberration.js';
import { FX_glitch } from './fxs/glitch.js';
import { FX_watermarkPlacement } from './fxs/watermarkPlacement.js'; import { FX_watermarkPlacement } from './fxs/watermarkPlacement.js';
type ParamTypeToPrimitive = { type ParamTypeToPrimitive = {
'number': number; 'number': number;
'boolean': boolean; 'boolean': boolean;
'align': { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; }; 'align': { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; };
'seed': number;
}; };
type ImageEffectorFxParamDefs = Record<string, { type ImageEffectorFxParamDefs = Record<string, {
@ -46,6 +48,7 @@ export type ImageEffectorFx<ID extends string, P extends ImageEffectorFxParamDef
export const FXS = [ export const FXS = [
FX_watermarkPlacement, FX_watermarkPlacement,
FX_chromaticAberration, FX_chromaticAberration,
FX_glitch,
] as const satisfies ImageEffectorFx<string, any>[]; ] as const satisfies ImageEffectorFx<string, any>[];
export type ImageEffectorLayerOf< export type ImageEffectorLayerOf<

View File

@ -28,24 +28,14 @@ void main() {
float normalisedValue = length((in_uv - 0.5) * 2.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); float strength = clamp((normalisedValue - u_start) * (1.0 / (1.0 - u_start)), 0.0, 1.0);
//vec2 vector = normalize((in_uv - (size / 2.0)) / size);
//vec2 vector = in_uv;
vec2 vector = (u_normalize ? normalize(in_uv - vec2(0.5)) : in_uv - vec2(0.5)); vec2 vector = (u_normalize ? normalize(in_uv - vec2(0.5)) : in_uv - vec2(0.5));
vec2 velocity = vector * strength * u_amount; vec2 velocity = vector * strength * u_amount;
//vec2 rOffset = -vector * strength * (u_amount * 1.0);
//vec2 gOffset = -vector * strength * (u_amount * 1.5);
//vec2 bOffset = -vector * strength * (u_amount * 2.0);
//vec2 rOffset = -vector * strength * (u_amount * 0.5);
//vec2 gOffset = -vector * strength * (u_amount * 1.0);
//vec2 bOffset = -vector * strength * (u_amount * 2.0);
vec2 rOffset = -vector * strength * (u_amount * r_strength); vec2 rOffset = -vector * strength * (u_amount * r_strength);
vec2 gOffset = -vector * strength * (u_amount * g_strength); vec2 gOffset = -vector * strength * (u_amount * g_strength);
vec2 bOffset = -vector * strength * (u_amount * b_strength); vec2 bOffset = -vector * strength * (u_amount * b_strength);
for (int i=0; i < samples; i++) { for (int i = 0; i < samples; i++) {
accumulator.r += texture(u_texture, in_uv + rOffset).r; accumulator.r += texture(u_texture, in_uv + rOffset).r;
rOffset -= velocity / float(samples); rOffset -= velocity / float(samples);

View File

@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import seedrandom from 'seedrandom';
import { defineImageEffectorFx } from '../ImageEffector.js';
const shader = `#version 300 es
precision highp float;
in vec2 in_uv;
uniform sampler2D u_texture;
uniform vec2 u_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(u_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r;
float g = texture(u_texture, vec2(in_uv.x + v, in_uv.y)).g;
float b = texture(u_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b;
float a = texture(u_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,
shader,
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, params, preTexture }) => {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, preTexture);
const u_texture = gl.getUniformLocation(program, 'u_texture');
gl.uniform1i(u_texture, 0);
const u_amount = gl.getUniformLocation(program, 'u_amount');
gl.uniform1i(u_amount, params.amount);
const u_channelShift = gl.getUniformLocation(program, 'u_channelShift');
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));
}
},
});