This commit is contained in:
syuilo 2025-06-03 11:18:13 +09:00
parent 62bd14ef88
commit 1f4170c259
8 changed files with 203 additions and 4 deletions

20
locales/index.d.ts vendored
View File

@ -12085,6 +12085,22 @@ export interface Locale extends ILocale {
*
*/
"advanced": string;
/**
*
*/
"stripe": string;
/**
*
*/
"stripeWidth": string;
/**
*
*/
"stripeFrequency": string;
/**
*
*/
"angle": string;
};
"_imageEffector": {
/**
@ -12140,6 +12156,10 @@ export interface Locale extends ILocale {
*
*/
"zoomLines": string;
/**
*
*/
"stripe": string;
};
};
}

View File

@ -3236,6 +3236,10 @@ _watermarkEditor:
type: "タイプ"
image: "画像"
advanced: "高度"
stripe: "ストライプ"
stripeWidth: "ラインの幅"
stripeFrequency: "ラインの数"
angle: "角度"
_imageEffector:
title: "エフェクト"
@ -3253,3 +3257,4 @@ _imageEffector:
distort: "歪み"
threshold: "二値化"
zoomLines: "集中線"
stripe: "ストライプ"

View File

@ -85,6 +85,48 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts._watermarkEditor.cover }}</template>
</MkSwitch>
</template>
<template v-else-if="layer.type === 'stripe'">
<MkRange
v-model="layer.frequency"
:min="1"
:max="30"
:step="0.01"
continuousUpdate
>
<template #label>{{ i18n.ts._watermarkEditor.stripeFrequency }}</template>
</MkRange>
<MkRange
v-model="layer.threshold"
:min="0"
:max="1"
:step="0.01"
continuousUpdate
>
<template #label>{{ i18n.ts._watermarkEditor.stripeWidth }}</template>
</MkRange>
<MkRange
v-model="layer.angle"
:min="0"
:max="1"
:step="0.01"
continuousUpdate
>
<template #label>{{ i18n.ts._watermarkEditor.angle }}</template>
</MkRange>
<MkRange
v-model="layer.opacity"
:min="0"
:max="1"
:step="0.01"
:textConverter="(v) => (v * 100).toFixed(1) + '%'"
continuousUpdate
>
<template #label>{{ i18n.ts._watermarkEditor.opacity }}</template>
</MkRange>
</template>
</div>
</template>

View File

@ -46,6 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>
<div v-if="layer.type === 'text'">{{ i18n.ts._watermarkEditor.text }}</div>
<div v-if="layer.type === 'image'">{{ i18n.ts._watermarkEditor.image }}</div>
<div v-if="layer.type === 'stripe'">{{ i18n.ts._watermarkEditor.stripe }}</div>
</template>
<template #footer>
<div class="_buttons">
@ -112,6 +113,18 @@ function createImageLayer(): WatermarkPreset['layers'][number] {
};
}
function createStripeLayer(): WatermarkPreset['layers'][number] {
return {
id: genId(),
type: 'stripe',
angle: 0.5,
frequency: 10,
threshold: 0.1,
black: false,
opacity: 0.75,
};
}
const props = defineProps<{
preset?: WatermarkPreset | null;
image?: File | null;
@ -281,6 +294,11 @@ function addLayer(ev: MouseEvent) {
action: () => {
preset.layers.push(createImageLayer());
},
}, {
text: i18n.ts._watermarkEditor.stripe,
action: () => {
preset.layers.push(createStripeLayer());
},
}], ev.currentTarget ?? ev.target);
}

View File

@ -11,6 +11,7 @@ import { FX_glitch } from './fxs/glitch.js';
import { FX_grayscale } from './fxs/grayscale.js';
import { FX_invert } from './fxs/invert.js';
import { FX_mirror } from './fxs/mirror.js';
import { FX_stripe } from './fxs/stripe.js';
import { FX_threshold } from './fxs/threshold.js';
import { FX_watermarkPlacement } from './fxs/watermarkPlacement.js';
import { FX_zoomLines } from './fxs/zoomLines.js';
@ -28,4 +29,5 @@ export const FXS = [
FX_distort,
FX_threshold,
FX_zoomLines,
FX_stripe,
] as const satisfies ImageEffectorFx<string, any>[];

View File

@ -0,0 +1,91 @@
/*
* 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 bool u_black;
uniform float u_opacity;
out vec4 out_color;
void main() {
vec4 in_color = texture(in_texture, in_uv);
float angle = u_angle * PI;
mat2 rotationMatrix = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
vec2 rotatedUV = rotationMatrix * (in_uv - 0.5);
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_black ? 0.0 : 1.0, value * u_opacity),
mix(in_color.g, u_black ? 0.0 : 1.0, value * u_opacity),
mix(in_color.b, u_black ? 0.0 : 1.0, 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', 'black', 'opacity'] as const,
params: {
angle: {
type: 'number' as const,
default: 0.5,
min: 0.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,
},
black: {
type: 'boolean' as const,
default: false,
},
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.uniform1i(u.black, params.black ? 1 : 0);
gl.uniform1f(u.opacity, params.opacity);
},
});

View File

@ -24,7 +24,7 @@ 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;
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(
@ -69,7 +69,7 @@ export const FX_zoomLines = defineImageEffectorFx({
},
threshold: {
type: 'number' as const,
default: 0.8,
default: 0.2,
min: 0.0,
max: 1.0,
step: 0.01,

View File

@ -4,6 +4,7 @@
*/
import { FX_watermarkPlacement } from './image-effector/fxs/watermarkPlacement.js';
import { FX_stripe } from './image-effector/fxs/stripe.js';
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
@ -28,6 +29,14 @@ export type WatermarkPreset = {
scale: number;
align: { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom' };
opacity: number;
} | {
id: string;
type: 'stripe';
angle: number;
frequency: number;
threshold: number;
black: boolean;
opacity: number;
})[];
};
@ -46,7 +55,7 @@ export class WatermarkRenderer {
renderWidth: options.renderWidth,
renderHeight: options.renderHeight,
image: options.image,
fxs: [FX_watermarkPlacement],
fxs: [FX_watermarkPlacement, FX_stripe],
});
}
@ -68,7 +77,7 @@ export class WatermarkRenderer {
},
},
};
} else {
} else if (layer.type === 'image') {
return {
fxId: 'watermarkPlacement',
id: layer.id,
@ -84,6 +93,18 @@ export class WatermarkRenderer {
},
},
};
} else if (layer.type === 'stripe') {
return {
fxId: 'stripe',
id: layer.id,
params: {
angle: layer.angle,
frequency: layer.frequency,
threshold: layer.threshold,
black: layer.black,
opacity: layer.opacity,
},
};
}
});
}