Compare commits

...

8 Commits

Author SHA1 Message Date
syuilo 40e35c051a
Update CHANGELOG.md 2025-06-15 11:10:03 +09:00
syuilo b93717be33
Update CHANGELOG.md
Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
2025-06-15 11:08:53 +09:00
syuilo fe805fb7f0 enhance(frontend/image-effector): tweak fxs 2025-06-15 11:06:46 +09:00
syuilo e9af9d4451 enhance(frontend/image-effector): tweak fxs 2025-06-15 10:57:29 +09:00
syuilo ce90fee586 enhance(frontend/image-effector): add blockNoise fx 2025-06-15 10:55:11 +09:00
syuilo 5bec8ba6b0 enhance(frontend/image-effector): tweak fxs 2025-06-15 10:19:42 +09:00
syuilo 3dbfd80d65 enhance(frontend/image-effector): tweak colorAdjust fx 2025-06-15 09:25:57 +09:00
syuilo b33eeb1366 enhance(frontend/image-effector): tweak distort fx 2025-06-15 08:47:59 +09:00
16 changed files with 216 additions and 53 deletions

View File

@ -1,11 +1,12 @@
## 2025.6.1
### Note
- Misskey Webプラグインのnote_view_interruptorは不具合の影響により現在一時的に無効化されています。
- AiScript Misskey拡張APIMisskey Webプラグイン[note_view_interruptor](https://misskey-hub.net/ja/docs/for-developers/plugin/plugin-api-reference/#pluginregister_note_view_interruptorfn)は不具合の影響により現在一時的に無効化されています。
- Misskey Web投稿フォームのプレビュー切り替えは「...」メニュー内に配置されました
### Client
- Feat: 画像にウォーターマークを付与できるようになりました
- Feat: 画像の加工ができるようになりました(実験的)
- Enhance: ノートのリアクション一覧で、押せるリアクションを優先して表示できるようにするオプションを追加
- Enhance: 全てのチャットメッセージを既読にできるように(設定→その他)
- Enhance: ミュートした絵文字をデバイス間で同期できるように

8
locales/index.d.ts vendored
View File

@ -12220,6 +12220,14 @@ export interface Locale extends ILocale {
*
*/
"checker": string;
/**
*
*/
"blockNoise": string;
/**
*
*/
"tearing": string;
};
};
}

View File

@ -3273,3 +3273,5 @@ _imageEffector:
stripe: "ストライプ"
polkadot: "ポルカドット"
checker: "チェッカー"
blockNoise: "ブロックノイズ"
tearing: "ティアリング"

View File

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-if="v.type === 'boolean'"
v-model="layer.params[k]"
>
<template #label>{{ k }}</template>
<template #label>{{ fx.params[k].label ?? k }}</template>
</MkSwitch>
<MkRange
v-else-if="v.type === 'number'"
@ -29,6 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:min="v.min"
:max="v.max"
:step="v.step"
:textConverter="fx.params[k].toViewValue"
@thumbDoubleClicked="() => {
if (fx.params[k].default != null) {
layer.params[k] = fx.params[k].default;
@ -37,13 +38,13 @@ SPDX-License-Identifier: AGPL-3.0-only
}
}"
>
<template #label>{{ k }}</template>
<template #label>{{ fx.params[k].label ?? k }}</template>
</MkRange>
<MkRadios
v-else-if="v.type === 'number:enum'"
v-model="layer.params[k]"
>
<template #label>{{ k }}</template>
<template #label>{{ fx.params[k].label ?? k }}</template>
<option v-for="item in v.enum" :value="item.value">{{ item.label }}</option>
</MkRadios>
<div v-else-if="v.type === 'seed'">
@ -55,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:max="10000"
:step="1"
>
<template #label>{{ k }}</template>
<template #label>{{ fx.params[k].label ?? k }}</template>
</MkRange>
</div>
<MkInput
@ -64,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
type="color"
@update:modelValue="v => { const c = getRgb(v); if (c != null) layer.params[k] = c; }"
>
<template #label>{{ k }}</template>
<template #label>{{ fx.params[k].label ?? k }}</template>
</MkInput>
</div>
</div>

View File

@ -96,7 +96,7 @@ watch(layers, async () => {
}, { deep: true });
function addEffect(ev: MouseEvent) {
os.popupMenu(FXS.filter(fx => fx.id !== 'watermarkPlacement').map((fx) => ({
os.popupMenu(FXS.map((fx) => ({
text: fx.name,
action: () => {
layers.push({

View File

@ -19,6 +19,8 @@ type ParamTypeToPrimitive = {
type ImageEffectorFxParamDefs = Record<string, {
type: keyof ParamTypeToPrimitive;
default: any;
label?: string;
toViewValue?: (v: any) => string;
}>;
export function defineImageEffectorFx<ID extends string, PS extends ImageEffectorFxParamDefs, US extends string[]>(fx: ImageEffectorFx<ID, PS, US>) {

View File

@ -10,20 +10,17 @@ import { FX_colorClamp } from './fxs/colorClamp.js';
import { FX_colorClampAdvanced } from './fxs/colorClampAdvanced.js';
import { FX_distort } from './fxs/distort.js';
import { FX_polkadot } from './fxs/polkadot.js';
import { FX_glitch } from './fxs/glitch.js';
import { FX_tearing } from './fxs/tearing.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';
import { FX_blockNoise } from './fxs/blockNoise.js';
import type { ImageEffectorFx } from './ImageEffector.js';
export const FXS = [
FX_watermarkPlacement,
FX_chromaticAberration,
FX_glitch,
FX_mirror,
FX_invert,
FX_grayscale,
@ -36,4 +33,7 @@ export const FXS = [
FX_stripe,
FX_polkadot,
FX_checker,
FX_chromaticAberration,
FX_tearing,
FX_blockNoise,
] as const satisfies ImageEffectorFx<string, any>[];

View File

@ -0,0 +1,119 @@
/*
* 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 vec2 u_shiftOrigins[128];
uniform vec2 u_shiftSizes[128];
uniform float u_channelShift;
out vec4 out_color;
void main() {
// TODO: ピクセル毎に計算する必要はないのでuniformにする
float aspect_ratio = min(in_resolution.x, in_resolution.y) / max(in_resolution.x, in_resolution.y);
float aspect_ratio_x = in_resolution.x > in_resolution.y ? 1.0 : aspect_ratio;
float aspect_ratio_y = in_resolution.x < in_resolution.y ? 1.0 : aspect_ratio;
float v = 0.0;
for (int i = 0; i < u_amount; i++) {
if (
in_uv.x * aspect_ratio_x > ((u_shiftOrigins[i].x * aspect_ratio_x) - u_shiftSizes[i].x) &&
in_uv.x * aspect_ratio_x < ((u_shiftOrigins[i].x * aspect_ratio_x) + u_shiftSizes[i].x) &&
in_uv.y * aspect_ratio_y > ((u_shiftOrigins[i].y * aspect_ratio_y) - u_shiftSizes[i].y) &&
in_uv.y * aspect_ratio_y < ((u_shiftOrigins[i].y * aspect_ratio_y) + u_shiftSizes[i].y)
) {
v += u_shiftStrengths[i];
}
}
float r = texture(in_texture, vec2(in_uv.x + (v * (1.0 + u_channelShift)), in_uv.y)).r;
float g = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).g;
float b = texture(in_texture, vec2(in_uv.x + (v * (1.0 + (u_channelShift / 2.0))), in_uv.y)).b;
float a = texture(in_texture, vec2(in_uv.x + v, in_uv.y)).a;
out_color = vec4(r, g, b, a);
}
`;
export const FX_blockNoise = defineImageEffectorFx({
id: 'blockNoise' as const,
name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.blockNoise,
shader,
uniforms: ['amount', 'channelShift'] as const,
params: {
amount: {
type: 'number' as const,
default: 50,
min: 1,
max: 100,
step: 1,
},
strength: {
type: 'number' as const,
default: 0.05,
min: -1,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
width: {
type: 'number' as const,
default: 0.05,
min: 0.01,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
height: {
type: 'number' as const,
default: 0.01,
min: 0.01,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
channelShift: {
type: 'number' as const,
default: 0,
min: 0,
max: 10,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
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 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);
}
},
});

View File

@ -58,6 +58,7 @@ export const FX_checker = defineImageEffectorFx({
min: -1.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 90) + '°',
},
scale: {
type: 'number' as const,
@ -76,6 +77,7 @@ export const FX_checker = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
},
main: ({ gl, u, params }) => {

View File

@ -72,7 +72,7 @@ void main() {
vec3 color = in_color.rgb;
color = color * u_brightness;
color += vec3(clamp(u_lightness, 0.0, 2.0) - 1.0);
color += vec3(u_lightness);
color = (color - 0.5) * u_contrast + 0.5;
vec3 hsl = rgb2hsl(color);
@ -92,45 +92,50 @@ export const FX_colorAdjust = defineImageEffectorFx({
params: {
lightness: {
type: 'number' as const,
default: 100,
min: 0,
max: 200,
step: 1,
default: 0,
min: -1,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
contrast: {
type: 'number' as const,
default: 100,
default: 1,
min: 0,
max: 200,
step: 1,
max: 4,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
hue: {
type: 'number' as const,
default: 0,
min: -360,
max: 360,
step: 1,
min: -1,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 180) + '°',
},
brightness: {
type: 'number' as const,
default: 100,
default: 1,
min: 0,
max: 200,
step: 1,
max: 4,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
saturation: {
type: 'number' as const,
default: 100,
default: 1,
min: 0,
max: 200,
step: 1,
max: 4,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.brightness, params.brightness / 100);
gl.uniform1f(u.contrast, params.contrast / 100);
gl.uniform1f(u.hue, params.hue / 360);
gl.uniform1f(u.lightness, params.lightness / 100);
gl.uniform1f(u.saturation, params.saturation / 100);
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);
},
});

View File

@ -37,6 +37,7 @@ export const FX_colorClamp = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
min: {
type: 'number' as const,
@ -44,6 +45,7 @@ export const FX_colorClamp = defineImageEffectorFx({
min: -1.0,
max: 0.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
},
main: ({ gl, u, params }) => {

View File

@ -41,6 +41,7 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
rMin: {
type: 'number' as const,
@ -48,6 +49,7 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({
min: -1.0,
max: 0.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
gMax: {
type: 'number' as const,
@ -55,6 +57,7 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
gMin: {
type: 'number' as const,
@ -62,6 +65,7 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({
min: -1.0,
max: 0.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
bMax: {
type: 'number' as const,
@ -69,6 +73,7 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
bMin: {
type: 'number' as const,
@ -76,6 +81,7 @@ export const FX_colorClampAdvanced = defineImageEffectorFx({
min: -1.0,
max: 0.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
},
main: ({ gl, u, params }) => {

View File

@ -9,6 +9,10 @@ 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;
@ -20,8 +24,8 @@ 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;
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));
@ -38,32 +42,34 @@ export const FX_distort = defineImageEffectorFx({
direction: {
type: 'number:enum' as const,
enum: [{ value: 0, label: 'v' }, { value: 1, label: 'h' }],
default: 0,
default: 1,
},
phase: {
type: 'number' as const,
default: 50.0,
min: 0.0,
max: 100,
default: 0.0,
min: -1.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
frequency: {
type: 'number' as const,
default: 50,
default: 30,
min: 0,
max: 100,
step: 0.1,
},
strength: {
type: 'number' as const,
default: 0.1,
default: 0.05,
min: 0,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
},
main: ({ gl, u, params }) => {
gl.uniform1f(u.phase, params.phase / 10);
gl.uniform1f(u.phase, params.phase);
gl.uniform1f(u.frequency, params.frequency);
gl.uniform1f(u.strength, params.strength);
gl.uniform1i(u.direction, params.direction);

View File

@ -90,6 +90,7 @@ export const FX_polkadot = defineImageEffectorFx({
min: -1.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 90) + '°',
},
scale: {
type: 'number' as const,
@ -111,6 +112,7 @@ export const FX_polkadot = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
minorDivisions: {
type: 'number' as const,
@ -132,6 +134,7 @@ export const FX_polkadot = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
color: {
type: 'color' as const,

View File

@ -60,6 +60,7 @@ export const FX_stripe = defineImageEffectorFx({
min: -1.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 90) + '°',
},
frequency: {
type: 'number' as const,
@ -74,6 +75,7 @@ export const FX_stripe = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
color: {
type: 'color' as const,
@ -85,6 +87,7 @@ export const FX_stripe = defineImageEffectorFx({
min: 0.0,
max: 1.0,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
},
main: ({ gl, u, params }) => {

View File

@ -37,9 +37,9 @@ void main() {
}
`;
export const FX_glitch = defineImageEffectorFx({
id: 'glitch' as const,
name: i18n.ts._imageEffector._fxs.glitch,
export const FX_tearing = defineImageEffectorFx({
id: 'tearing' as const,
name: i18n.ts._imageEffector._fxs.glitch + ': ' + i18n.ts._imageEffector._fxs.tearing,
shader,
uniforms: ['amount', 'channelShift'] as const,
params: {
@ -52,17 +52,19 @@ export const FX_glitch = defineImageEffectorFx({
},
strength: {
type: 'number' as const,
default: 5,
min: -100,
max: 100,
default: 0.05,
min: -1,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
size: {
type: 'number' as const,
default: 20,
default: 0.2,
min: 0,
max: 100,
max: 1,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
channelShift: {
type: 'number' as const,
@ -70,6 +72,7 @@ export const FX_glitch = defineImageEffectorFx({
min: 0,
max: 10,
step: 0.01,
toViewValue: v => Math.round(v * 100) + '%',
},
seed: {
type: 'seed' as const,
@ -87,10 +90,10 @@ export const FX_glitch = defineImageEffectorFx({
gl.uniform1f(o, rnd());
const s = gl.getUniformLocation(program, `u_shiftStrengths[${i.toString()}]`);
gl.uniform1f(s, (1 - (rnd() * 2)) * (params.strength / 100));
gl.uniform1f(s, (1 - (rnd() * 2)) * params.strength);
const h = gl.getUniformLocation(program, `u_shiftHeights[${i.toString()}]`);
gl.uniform1f(h, rnd() * (params.size / 100));
gl.uniform1f(h, rnd() * params.size);
}
},
});