From ed02cf6677a689fa78a9b04aa6fb0ad3e9cc6e43 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:58:55 +0900 Subject: [PATCH] wip --- locales/index.d.ts | 16 ++++- locales/ja-JP.yml | 2 +- .../src/components/MkImageEffectorDialog.vue | 63 +++++++++++----- .../src/utility/image-effector/fxs.ts | 8 +-- .../src/utility/image-effector/fxs/blur.ts | 72 ++++++++++++++++++- .../fxs/{fillSquare.ts => fill.ts} | 6 +- 6 files changed, 138 insertions(+), 29 deletions(-) rename packages/frontend/src/utility/image-effector/fxs/{fillSquare.ts => fill.ts} (96%) diff --git a/locales/index.d.ts b/locales/index.d.ts index 95886125ff..08ec55a62d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -12346,6 +12346,10 @@ export interface Locale extends ILocale { * 白黒 */ "grayscale": string; + /** + * ぼかし + */ + "blur": string; /** * 色調補正 */ @@ -12391,9 +12395,9 @@ export interface Locale extends ILocale { */ "tearing": string; /** - * 塗りつぶし(四角) + * 塗りつぶし */ - "fillSquare": string; + "fill": string; }; "_fxProps": { /** @@ -12408,6 +12412,14 @@ export interface Locale extends ILocale { * サイズ */ "size": string; + /** + * 半径 + */ + "radius": string; + /** + * サンプル数 + */ + "samples": string; /** * 位置 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 16d1fe0ac6..7b86fd906d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -3318,7 +3318,7 @@ _imageEffector: checker: "チェッカー" blockNoise: "ブロックノイズ" tearing: "ティアリング" - fillSquare: "塗りつぶし(四角)" + fill: "塗りつぶし" _fxProps: angle: "角度" diff --git a/packages/frontend/src/components/MkImageEffectorDialog.vue b/packages/frontend/src/components/MkImageEffectorDialog.vue index 465100ef20..96fb01bb8c 100644 --- a/packages/frontend/src/components/MkImageEffectorDialog.vue +++ b/packages/frontend/src/components/MkImageEffectorDialog.vue @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.preview }}
- +
@@ -216,10 +216,24 @@ watch(enabled, () => { } }); -const fillSquare = ref(false); +const penMode = ref<'fill' | 'blur' | null>(null); + +function showPenMenu(ev: MouseEvent) { + os.popupMenu([{ + text: i18n.ts._imageEffector._fxs.fill, + action: () => { + penMode.value = 'fill'; + }, + }, { + text: i18n.ts._imageEffector._fxs.blur, + action: () => { + penMode.value = 'blur'; + }, + }], ev.currentTarget ?? ev.target); +} function onImagePointerdown(ev: PointerEvent) { - if (canvasEl.value == null || imageBitmap == null || !fillSquare.value) return; + if (canvasEl.value == null || imageBitmap == null || penMode.value == null) return; const AW = canvasEl.value.clientWidth; const AH = canvasEl.value.clientHeight; @@ -250,19 +264,34 @@ function onImagePointerdown(ev: PointerEvent) { } const id = genId(); - layers.push({ - id, - fxId: 'fillSquare', - params: { - offsetX: 0, - offsetY: 0, - scaleX: 0.1, - scaleY: 0.1, - angle: 0, - opacity: 1, - color: [1, 1, 1], - }, - }); + if (penMode.value === 'fill') { + layers.push({ + id, + fxId: 'fill', + params: { + offsetX: 0, + offsetY: 0, + scaleX: 0.1, + scaleY: 0.1, + angle: 0, + opacity: 1, + color: [1, 1, 1], + }, + }); + } else if (penMode.value === 'blur') { + layers.push({ + id, + fxId: 'blur', + params: { + offsetX: 0, + offsetY: 0, + scaleX: 0.1, + scaleY: 0.1, + angle: 0, + radius: 3, + }, + }); + } _move(ev.offsetX, ev.offsetY); @@ -302,7 +331,7 @@ function onImagePointerdown(ev: PointerEvent) { canvasEl.value?.removeEventListener('pointercancel', up); canvasEl.value?.releasePointerCapture(ev.pointerId); - fillSquare.value = false; + penMode.value = null; } canvasEl.value.addEventListener('pointermove', move); diff --git a/packages/frontend/src/utility/image-effector/fxs.ts b/packages/frontend/src/utility/image-effector/fxs.ts index b954bd370d..83ec20823d 100644 --- a/packages/frontend/src/utility/image-effector/fxs.ts +++ b/packages/frontend/src/utility/image-effector/fxs.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { FX_blur } from './fxs/blur.js'; import { FX_checker } from './fxs/checker.js'; import { FX_chromaticAberration } from './fxs/chromaticAberration.js'; import { FX_colorAdjust } from './fxs/colorAdjust.js'; @@ -19,14 +18,14 @@ import { FX_stripe } from './fxs/stripe.js'; import { FX_threshold } from './fxs/threshold.js'; import { FX_zoomLines } from './fxs/zoomLines.js'; import { FX_blockNoise } from './fxs/blockNoise.js'; -import { FX_fillSquare } from './fxs/fillSquare.js'; +import { FX_fill } from './fxs/fill.js'; +import { FX_blur } from './fxs/blur.js'; import type { ImageEffectorFx } from './ImageEffector.js'; export const FXS = [ FX_mirror, FX_invert, FX_grayscale, - FX_blur, FX_colorAdjust, FX_colorClamp, FX_colorClampAdvanced, @@ -39,5 +38,6 @@ export const FXS = [ FX_chromaticAberration, FX_tearing, FX_blockNoise, - FX_fillSquare, + FX_fill, + FX_blur, ] as const satisfies ImageEffectorFx[]; diff --git a/packages/frontend/src/utility/image-effector/fxs/blur.ts b/packages/frontend/src/utility/image-effector/fxs/blur.ts index 8febe39046..862bbbf65d 100644 --- a/packages/frontend/src/utility/image-effector/fxs/blur.ts +++ b/packages/frontend/src/utility/image-effector/fxs/blur.ts @@ -9,14 +9,34 @@ 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 vec2 u_offset; +uniform vec2 u_scale; +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 = 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; @@ -57,18 +77,66 @@ export const FX_blur = defineImageEffectorFx({ id: 'blur', name: i18n.ts._imageEffector._fxs.blur, shader, - uniforms: ['radius', 'samples'] as const, + uniforms: ['offset', 'scale', 'angle', 'radius', 'samples'] as const, 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 + ' X', + 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 + ' Y', + type: 'number', + default: 0.5, + min: 0.0, + max: 1.0, + step: 0.01, + toViewValue: v => Math.round(v * 100) + '%', + }, + 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: 20.0, + max: 10.0, step: 0.5, }, }, 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.uniform1f(u.angle, params.angle / 2); gl.uniform1f(u.radius, params.radius); gl.uniform1i(u.samples, 256); }, diff --git a/packages/frontend/src/utility/image-effector/fxs/fillSquare.ts b/packages/frontend/src/utility/image-effector/fxs/fill.ts similarity index 96% rename from packages/frontend/src/utility/image-effector/fxs/fillSquare.ts rename to packages/frontend/src/utility/image-effector/fxs/fill.ts index 55b53830e0..70e0953562 100644 --- a/packages/frontend/src/utility/image-effector/fxs/fillSquare.ts +++ b/packages/frontend/src/utility/image-effector/fxs/fill.ts @@ -46,9 +46,9 @@ void main() { } `; -export const FX_fillSquare = defineImageEffectorFx({ - id: 'fillSquare', - name: i18n.ts._imageEffector._fxs.fillSquare, +export const FX_fill = defineImageEffectorFx({ + id: 'fill', + name: i18n.ts._imageEffector._fxs.fill, shader, uniforms: ['offset', 'scale', 'angle', 'color', 'opacity'] as const, params: {