Compare commits
	
		
			4 Commits
		
	
	
		
			493740a149
			...
			534fb40a35
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 534fb40a35 | |
|  | b8ae7edcec | |
|  | e24233c1c7 | |
|  | 225154d76d | 
|  | @ -13,6 +13,7 @@ | ||||||
| - Feat: 動画を圧縮してアップロードできるようになりました | - Feat: 動画を圧縮してアップロードできるようになりました | ||||||
| - Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました | - Enhance: チャットの日本語名称がダイレクトメッセージに戻るとともに、ベータ版機能ではなくなりました | ||||||
| - Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし、モザイク)を追加 | - Enhance: 画像編集にマスクエフェクト(塗りつぶし、ぼかし、モザイク)を追加 | ||||||
|  | - Enhance: 画像編集の集中線エフェクトを強化 | ||||||
| - Enhance: ウォーターマークにアカウントのQRコードを追加できるように | - Enhance: ウォーターマークにアカウントのQRコードを追加できるように | ||||||
| - Enhance: テーマをドラッグ&ドロップできるように | - Enhance: テーマをドラッグ&ドロップできるように | ||||||
| - Enhance: 絵文字ピッカーのサイズをより大きくできるように | - Enhance: 絵文字ピッカーのサイズをより大きくできるように | ||||||
|  |  | ||||||
|  | @ -0,0 +1,232 @@ | ||||||
|  | <!-- | ||||||
|  | SPDX-FileCopyrightText: syuilo and misskey-project | ||||||
|  | SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  | <canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { onMounted, onUnmounted, useTemplateRef } from 'vue'; | ||||||
|  | import isChromatic from 'chromatic/isChromatic'; | ||||||
|  | import { initShaderProgram } from '@/utility/webgl.js'; | ||||||
|  | 
 | ||||||
|  | const VERTEX_SHADER = `#version 300 es | ||||||
|  | in vec2 position; | ||||||
|  | out vec2 in_uv; | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  | 	in_uv = (position + 1.0) / 2.0; | ||||||
|  | 	gl_Position = vec4(position, 0.0, 1.0); | ||||||
|  | } | ||||||
|  | `; | ||||||
|  | 
 | ||||||
|  | const FRAGMENT_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 vec2 in_resolution; | ||||||
|  | uniform float u_scale; | ||||||
|  | uniform float u_time; | ||||||
|  | uniform float u_seed; | ||||||
|  | uniform float u_angle; | ||||||
|  | uniform float u_radius; | ||||||
|  | uniform vec3 u_color; | ||||||
|  | uniform vec2 u_ripplePositions[16]; | ||||||
|  | uniform float u_rippleRadiuses[16]; | ||||||
|  | out vec4 out_color; | ||||||
|  | 
 | ||||||
|  | float getRipple(vec2 uv) { | ||||||
|  | 	float strength = 0.0; | ||||||
|  | 	float thickness = 0.05; | ||||||
|  | 	for (int i = 0; i < 16; i++) { | ||||||
|  | 		if (u_rippleRadiuses[i] <= 0.0) continue; | ||||||
|  | 
 | ||||||
|  | 		float d = distance(uv, u_ripplePositions[i]); | ||||||
|  | 
 | ||||||
|  | 		// フチ | ||||||
|  | 		if (d < u_rippleRadiuses[i] + thickness && d > u_rippleRadiuses[i] - thickness) { | ||||||
|  | 			float gradate =  abs(d - u_rippleRadiuses[i] + thickness) / thickness; | ||||||
|  | 			strength += (1.0 - u_rippleRadiuses[i]) * gradate; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 内側 | ||||||
|  | 		if (d < u_rippleRadiuses[i] + thickness) { | ||||||
|  | 			strength += 0.25 * (1.0 - u_rippleRadiuses[i]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return strength; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  | 	float x_ratio = min(in_resolution.x / in_resolution.y, 1.0); | ||||||
|  | 	float y_ratio = min(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) | ||||||
|  | 	); | ||||||
|  | 	vec2 uv = rotatedUV; | ||||||
|  | 
 | ||||||
|  | 	float time = u_time * 0.00025; | ||||||
|  | 
 | ||||||
|  | 	float size = 1.0 / u_scale; | ||||||
|  | 	float size_half = size / 2.0; | ||||||
|  | 	float modX = mod(uv.x, size); | ||||||
|  | 	float modY = mod(uv.y, size); | ||||||
|  | 
 | ||||||
|  | 	vec2 pixelated_uv = vec2( | ||||||
|  | 		(size * (floor((uv.x - 0.5 - size) / size) + 0.5)), | ||||||
|  | 		(size * (floor((uv.y - 0.5 - size) / size) + 0.5)) | ||||||
|  | 	) + vec2(0.5 + size, 0.5 + size); | ||||||
|  | 
 | ||||||
|  | 	float strength = getRipple(pixelated_uv); | ||||||
|  | 
 | ||||||
|  | 	float opacity = min(max(strength, 0.0), 1.0); | ||||||
|  | 
 | ||||||
|  | 	float threshold = ((u_radius / 2.0) / u_scale); | ||||||
|  | 	if (length(vec2(modX - size_half, modY - size_half)) < threshold) { | ||||||
|  | 		out_color = vec4(u_color.r, u_color.g, u_color.b, opacity); | ||||||
|  | 		//out_color = vec4(1.0); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// debug | ||||||
|  | 	//float a = min(max(getRipple(uv), 0.0), 1.0); | ||||||
|  | 	//out_color = vec4(u_color.r, u_color.g, u_color.b, (opacity + a) / 2.0); | ||||||
|  | 
 | ||||||
|  | 	out_color = vec4(0.0, 0.0, 0.0, 0.0); | ||||||
|  | } | ||||||
|  | `; | ||||||
|  | 
 | ||||||
|  | const canvasEl = useTemplateRef('canvasEl'); | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<{ | ||||||
|  | 	scale?: number; | ||||||
|  | }>(), { | ||||||
|  | 	scale: 48, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null; | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  | 	const canvas = canvasEl.value!; | ||||||
|  | 	let width = canvas.offsetWidth; | ||||||
|  | 	let height = canvas.offsetHeight; | ||||||
|  | 	canvas.width = width; | ||||||
|  | 	canvas.height = height; | ||||||
|  | 
 | ||||||
|  | 	const maybeGl = canvas.getContext('webgl2', { preserveDrawingBuffer: false, alpha: true, premultipliedAlpha: false, antialias: true }); | ||||||
|  | 	if (maybeGl == null) return; | ||||||
|  | 
 | ||||||
|  | 	const gl = maybeGl; | ||||||
|  | 
 | ||||||
|  | 	const VERTICES = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]); | ||||||
|  | 	const vertexBuffer = gl.createBuffer(); | ||||||
|  | 	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); | ||||||
|  | 	gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW); | ||||||
|  | 
 | ||||||
|  | 	//gl.clearColor(0.0, 0.0, 0.0, 0.0); | ||||||
|  | 	//gl.clear(gl.COLOR_BUFFER_BIT); | ||||||
|  | 
 | ||||||
|  | 	const shaderProgram = initShaderProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER); | ||||||
|  | 
 | ||||||
|  | 	gl.useProgram(shaderProgram); | ||||||
|  | 
 | ||||||
|  | 	const positionLocation = gl.getAttribLocation(shaderProgram, 'position'); | ||||||
|  | 	gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); | ||||||
|  | 	gl.enableVertexAttribArray(positionLocation); | ||||||
|  | 
 | ||||||
|  | 	const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution'); | ||||||
|  | 	gl.uniform2fv(in_resolution, [canvas.width, canvas.height]); | ||||||
|  | 
 | ||||||
|  | 	const u_time = gl.getUniformLocation(shaderProgram, 'u_time'); | ||||||
|  | 	const u_seed = gl.getUniformLocation(shaderProgram, 'u_seed'); | ||||||
|  | 	const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale'); | ||||||
|  | 	const u_angle = gl.getUniformLocation(shaderProgram, 'u_angle'); | ||||||
|  | 	const u_radius = gl.getUniformLocation(shaderProgram, 'u_radius'); | ||||||
|  | 	const u_color = gl.getUniformLocation(shaderProgram, 'u_color'); | ||||||
|  | 	gl.uniform1f(u_seed, Math.random() * 1000); | ||||||
|  | 	gl.uniform1f(u_scale, props.scale); | ||||||
|  | 	gl.uniform1f(u_angle, 0.0); | ||||||
|  | 	gl.uniform1f(u_radius, 0.15); | ||||||
|  | 	gl.uniform3fv(u_color, [0.5, 1.0, 0]); | ||||||
|  | 
 | ||||||
|  | 	if (isChromatic()) { | ||||||
|  | 		gl.uniform1f(u_time, 0); | ||||||
|  | 		gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||||
|  | 	} else { | ||||||
|  | 		let ripples = [] as { position: [number, number]; startTime: number; }[]; | ||||||
|  | 		const LIFE_TIME = 1000 * 4; | ||||||
|  | 
 | ||||||
|  | 		function render(timeStamp: number) { | ||||||
|  | 			let sizeChanged = false; | ||||||
|  | 			if (Math.abs(height - canvas.offsetHeight) > 2) { | ||||||
|  | 				height = canvas.offsetHeight; | ||||||
|  | 				canvas.height = height; | ||||||
|  | 				sizeChanged = true; | ||||||
|  | 			} | ||||||
|  | 			if (Math.abs(width - canvas.offsetWidth) > 2) { | ||||||
|  | 				width = canvas.offsetWidth; | ||||||
|  | 				canvas.width = width; | ||||||
|  | 				sizeChanged = true; | ||||||
|  | 			} | ||||||
|  | 			if (sizeChanged && gl) { | ||||||
|  | 				gl.uniform2fv(in_resolution, [width, height]); | ||||||
|  | 				gl.viewport(0, 0, width, height); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			gl.uniform1f(u_time, timeStamp); | ||||||
|  | 
 | ||||||
|  | 			if (Math.random() < 0.01 && ripples.length < 16) { | ||||||
|  | 				ripples.push({ position: [(Math.random() * 2) - 1, (Math.random() * 2) - 1], startTime: timeStamp }); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for (let i = 0; i < 16; i++) { | ||||||
|  | 				const o = gl.getUniformLocation(shaderProgram, `u_ripplePositions[${i.toString()}]`); | ||||||
|  | 				const r = gl.getUniformLocation(shaderProgram, `u_rippleRadiuses[${i.toString()}]`); | ||||||
|  | 				const ripple = ripples[i]; | ||||||
|  | 				if (ripple == null) { | ||||||
|  | 					gl.uniform2f(o, 0, 0); | ||||||
|  | 					gl.uniform1f(r, 0.0); | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				const delta = timeStamp - ripple.startTime; | ||||||
|  | 
 | ||||||
|  | 				gl.uniform2f(o, ripple.position[0], ripple.position[1]); | ||||||
|  | 				gl.uniform1f(r, delta / LIFE_TIME); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ripples = ripples.filter(r => (timeStamp - r.startTime) < LIFE_TIME); | ||||||
|  | 			if (ripples.length === 0) { | ||||||
|  | 				ripples.push({ position: [(Math.random() * 2) - 1, (Math.random() * 2) - 1], startTime: timeStamp }); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||||
|  | 
 | ||||||
|  | 			handle = window.requestAnimationFrame(render); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		handle = window.requestAnimationFrame(render); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | onUnmounted(() => { | ||||||
|  | 	if (handle) { | ||||||
|  | 		window.cancelAnimationFrame(handle); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// TODO: WebGLリソースの解放 | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" module> | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,190 @@ | ||||||
|  | <!-- | ||||||
|  | SPDX-FileCopyrightText: syuilo and misskey-project | ||||||
|  | SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  | <canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { onMounted, onUnmounted, useTemplateRef } from 'vue'; | ||||||
|  | import isChromatic from 'chromatic/isChromatic'; | ||||||
|  | import { GLSL_LIB_SNOISE, initShaderProgram } from '@/utility/webgl.js'; | ||||||
|  | 
 | ||||||
|  | const VERTEX_SHADER = `#version 300 es | ||||||
|  | in vec2 position; | ||||||
|  | out vec2 in_uv; | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  | 	in_uv = (position + 1.0) / 2.0; | ||||||
|  | 	gl_Position = vec4(position, 0.0, 1.0); | ||||||
|  | } | ||||||
|  | `; | ||||||
|  | 
 | ||||||
|  | const FRAGMENT_SHADER = `#version 300 es | ||||||
|  | precision mediump float; | ||||||
|  | 
 | ||||||
|  | const float PI = 3.141592653589793; | ||||||
|  | const float TWO_PI = 6.283185307179586; | ||||||
|  | const float HALF_PI = 1.5707963267948966; | ||||||
|  | 
 | ||||||
|  | ${GLSL_LIB_SNOISE} | ||||||
|  | 
 | ||||||
|  | in vec2 in_uv; | ||||||
|  | uniform vec2 in_resolution; | ||||||
|  | uniform float u_scale; | ||||||
|  | uniform float u_time; | ||||||
|  | uniform float u_seed; | ||||||
|  | uniform float u_angle; | ||||||
|  | uniform float u_radius; | ||||||
|  | uniform vec3 u_color; | ||||||
|  | out vec4 out_color; | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  | 	float x_ratio = min(in_resolution.x / in_resolution.y, 1.0); | ||||||
|  | 	float y_ratio = min(in_resolution.y / in_resolution.x, 1.0); | ||||||
|  | 
 | ||||||
|  | 	float size = 1.0 / u_scale; | ||||||
|  | 	float size_half = size / 2.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) | ||||||
|  | 	); | ||||||
|  | 	vec2 uv = rotatedUV; | ||||||
|  | 
 | ||||||
|  | 	float modX = mod(uv.x, size); | ||||||
|  | 	float modY = mod(uv.y, size); | ||||||
|  | 
 | ||||||
|  | 	vec2 pixelated_uv = vec2( | ||||||
|  | 		(size * (floor((uv.x - 0.5 - size) / size) + 0.5)), | ||||||
|  | 		(size * (floor((uv.y - 0.5 - size) / size) + 0.5)) | ||||||
|  | 	) + vec2(0.5 + size, 0.5 + size); | ||||||
|  | 
 | ||||||
|  | 	float time = u_time * 0.00025; | ||||||
|  | 
 | ||||||
|  | 	float noiseAScale = 1.0; | ||||||
|  | 	float noiseAX = (pixelated_uv.x + u_seed) * (u_scale / noiseAScale); | ||||||
|  | 	float noiseAY = (pixelated_uv.y + u_seed) * (u_scale / noiseAScale); | ||||||
|  |   float noiseA = snoise(vec3(noiseAX, noiseAY, time * 2.0)); | ||||||
|  | 
 | ||||||
|  | 	float noiseBScale = 32.0; | ||||||
|  | 	float noiseBX = (pixelated_uv.x + u_seed) * (u_scale / noiseBScale); | ||||||
|  | 	float noiseBY = (pixelated_uv.y + u_seed) * (u_scale / noiseBScale); | ||||||
|  |   float noiseB = snoise(vec3(noiseBX, noiseBY, time)); | ||||||
|  | 
 | ||||||
|  | 	float strength = 0.0; | ||||||
|  | 	strength += noiseA * 0.2; | ||||||
|  | 	strength += noiseB * 0.8; | ||||||
|  | 
 | ||||||
|  | 	float opacity = min(max(strength, 0.0), 1.0); | ||||||
|  | 
 | ||||||
|  | 	float threshold = ((u_radius / 2.0) / u_scale); | ||||||
|  | 	if (length(vec2(modX - size_half, modY - size_half)) < threshold) { | ||||||
|  | 		out_color = vec4(u_color.r, u_color.g, u_color.b, opacity); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	out_color = vec4(0.0, 0.0, 0.0, 0.0); | ||||||
|  | } | ||||||
|  | `; | ||||||
|  | 
 | ||||||
|  | const canvasEl = useTemplateRef('canvasEl'); | ||||||
|  | 
 | ||||||
|  | const props = withDefaults(defineProps<{ | ||||||
|  | 	scale?: number; | ||||||
|  | }>(), { | ||||||
|  | 	scale: 48, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null; | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  | 	const canvas = canvasEl.value!; | ||||||
|  | 	let width = canvas.offsetWidth; | ||||||
|  | 	let height = canvas.offsetHeight; | ||||||
|  | 	canvas.width = width; | ||||||
|  | 	canvas.height = height; | ||||||
|  | 
 | ||||||
|  | 	const maybeGl = canvas.getContext('webgl2', { preserveDrawingBuffer: false, alpha: true, premultipliedAlpha: false, antialias: true }); | ||||||
|  | 	if (maybeGl == null) return; | ||||||
|  | 
 | ||||||
|  | 	const gl = maybeGl; | ||||||
|  | 
 | ||||||
|  | 	const VERTICES = new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]); | ||||||
|  | 	const vertexBuffer = gl.createBuffer(); | ||||||
|  | 	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); | ||||||
|  | 	gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW); | ||||||
|  | 
 | ||||||
|  | 	//gl.clearColor(0.0, 0.0, 0.0, 0.0); | ||||||
|  | 	//gl.clear(gl.COLOR_BUFFER_BIT); | ||||||
|  | 
 | ||||||
|  | 	const shaderProgram = initShaderProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER); | ||||||
|  | 
 | ||||||
|  | 	gl.useProgram(shaderProgram); | ||||||
|  | 
 | ||||||
|  | 	const positionLocation = gl.getAttribLocation(shaderProgram, 'position'); | ||||||
|  | 	gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); | ||||||
|  | 	gl.enableVertexAttribArray(positionLocation); | ||||||
|  | 
 | ||||||
|  | 	const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution'); | ||||||
|  | 	gl.uniform2fv(in_resolution, [canvas.width, canvas.height]); | ||||||
|  | 
 | ||||||
|  | 	const u_time = gl.getUniformLocation(shaderProgram, 'u_time'); | ||||||
|  | 	const u_seed = gl.getUniformLocation(shaderProgram, 'u_seed'); | ||||||
|  | 	const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale'); | ||||||
|  | 	const u_angle = gl.getUniformLocation(shaderProgram, 'u_angle'); | ||||||
|  | 	const u_radius = gl.getUniformLocation(shaderProgram, 'u_radius'); | ||||||
|  | 	const u_color = gl.getUniformLocation(shaderProgram, 'u_color'); | ||||||
|  | 	gl.uniform1f(u_seed, Math.random() * 1000); | ||||||
|  | 	gl.uniform1f(u_scale, props.scale); | ||||||
|  | 	gl.uniform1f(u_angle, 0.0); | ||||||
|  | 	gl.uniform1f(u_radius, 0.15); | ||||||
|  | 	gl.uniform3fv(u_color, [0.5, 1.0, 0]); | ||||||
|  | 
 | ||||||
|  | 	if (isChromatic()) { | ||||||
|  | 		gl.uniform1f(u_time, 0); | ||||||
|  | 		gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||||
|  | 	} else { | ||||||
|  | 		function render(timeStamp: number) { | ||||||
|  | 			let sizeChanged = false; | ||||||
|  | 			if (Math.abs(height - canvas.offsetHeight) > 2) { | ||||||
|  | 				height = canvas.offsetHeight; | ||||||
|  | 				canvas.height = height; | ||||||
|  | 				sizeChanged = true; | ||||||
|  | 			} | ||||||
|  | 			if (Math.abs(width - canvas.offsetWidth) > 2) { | ||||||
|  | 				width = canvas.offsetWidth; | ||||||
|  | 				canvas.width = width; | ||||||
|  | 				sizeChanged = true; | ||||||
|  | 			} | ||||||
|  | 			if (sizeChanged && gl) { | ||||||
|  | 				gl.uniform2fv(in_resolution, [width, height]); | ||||||
|  | 				gl.viewport(0, 0, width, height); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			gl.uniform1f(u_time, timeStamp); | ||||||
|  | 
 | ||||||
|  | 			gl.drawArrays(gl.TRIANGLES, 0, 6); | ||||||
|  | 
 | ||||||
|  | 			handle = window.requestAnimationFrame(render); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		handle = window.requestAnimationFrame(render); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | onUnmounted(() => { | ||||||
|  | 	if (handle) { | ||||||
|  | 		window.cancelAnimationFrame(handle); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// TODO: WebGLリソースの解放 | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" module> | ||||||
|  | </style> | ||||||
|  | @ -69,8 +69,8 @@ | ||||||
| 		"utf-8-validate": "6.0.5" | 		"utf-8-validate": "6.0.5" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@aws-sdk/client-s3": "3.883.0", | 		"@aws-sdk/client-s3": "3.893.0", | ||||||
| 		"@aws-sdk/lib-storage": "3.883.0", | 		"@aws-sdk/lib-storage": "3.893.0", | ||||||
| 		"@discordapp/twemoji": "16.0.1", | 		"@discordapp/twemoji": "16.0.1", | ||||||
| 		"@fastify/accepts": "5.0.2", | 		"@fastify/accepts": "5.0.2", | ||||||
| 		"@fastify/cookie": "11.0.2", | 		"@fastify/cookie": "11.0.2", | ||||||
|  | @ -82,7 +82,7 @@ | ||||||
| 		"@fastify/view": "10.0.2", | 		"@fastify/view": "10.0.2", | ||||||
| 		"@misskey-dev/sharp-read-bmp": "1.2.0", | 		"@misskey-dev/sharp-read-bmp": "1.2.0", | ||||||
| 		"@misskey-dev/summaly": "5.2.3", | 		"@misskey-dev/summaly": "5.2.3", | ||||||
| 		"@napi-rs/canvas": "0.1.79", | 		"@napi-rs/canvas": "0.1.80", | ||||||
| 		"@nestjs/common": "11.1.6", | 		"@nestjs/common": "11.1.6", | ||||||
| 		"@nestjs/core": "11.1.6", | 		"@nestjs/core": "11.1.6", | ||||||
| 		"@nestjs/testing": "11.1.6", | 		"@nestjs/testing": "11.1.6", | ||||||
|  | @ -103,11 +103,11 @@ | ||||||
| 		"bcryptjs": "2.4.3", | 		"bcryptjs": "2.4.3", | ||||||
| 		"blurhash": "2.0.5", | 		"blurhash": "2.0.5", | ||||||
| 		"body-parser": "1.20.3", | 		"body-parser": "1.20.3", | ||||||
| 		"bullmq": "5.58.5", | 		"bullmq": "5.58.7", | ||||||
| 		"cacheable-lookup": "7.0.0", | 		"cacheable-lookup": "7.0.0", | ||||||
| 		"cbor": "9.0.2", | 		"cbor": "9.0.2", | ||||||
| 		"chalk": "5.6.0", | 		"chalk": "5.6.2", | ||||||
| 		"chalk-template": "1.1.0", | 		"chalk-template": "1.1.2", | ||||||
| 		"chokidar": "4.0.3", | 		"chokidar": "4.0.3", | ||||||
| 		"cli-highlight": "2.1.11", | 		"cli-highlight": "2.1.11", | ||||||
| 		"color-convert": "2.0.1", | 		"color-convert": "2.0.1", | ||||||
|  | @ -120,7 +120,7 @@ | ||||||
| 		"file-type": "19.6.0", | 		"file-type": "19.6.0", | ||||||
| 		"fluent-ffmpeg": "2.1.3", | 		"fluent-ffmpeg": "2.1.3", | ||||||
| 		"form-data": "4.0.4", | 		"form-data": "4.0.4", | ||||||
| 		"got": "14.4.8", | 		"got": "14.4.9", | ||||||
| 		"happy-dom": "16.8.1", | 		"happy-dom": "16.8.1", | ||||||
| 		"hpagent": "1.2.0", | 		"hpagent": "1.2.0", | ||||||
| 		"htmlescape": "1.1.1", | 		"htmlescape": "1.1.1", | ||||||
|  | @ -135,7 +135,7 @@ | ||||||
| 		"jsonld": "8.3.3", | 		"jsonld": "8.3.3", | ||||||
| 		"jsrsasign": "11.1.0", | 		"jsrsasign": "11.1.0", | ||||||
| 		"juice": "11.0.1", | 		"juice": "11.0.1", | ||||||
| 		"meilisearch": "0.52.0", | 		"meilisearch": "0.53.0", | ||||||
| 		"mfm-js": "0.25.0", | 		"mfm-js": "0.25.0", | ||||||
| 		"microformats-parser": "2.0.4", | 		"microformats-parser": "2.0.4", | ||||||
| 		"mime-types": "2.1.35", | 		"mime-types": "2.1.35", | ||||||
|  | @ -175,12 +175,12 @@ | ||||||
| 		"slacc": "0.0.10", | 		"slacc": "0.0.10", | ||||||
| 		"strict-event-emitter-types": "2.0.0", | 		"strict-event-emitter-types": "2.0.0", | ||||||
| 		"stringz": "2.1.0", | 		"stringz": "2.1.0", | ||||||
| 		"systeminformation": "5.27.8", | 		"systeminformation": "5.27.10", | ||||||
| 		"tinycolor2": "1.6.0", | 		"tinycolor2": "1.6.0", | ||||||
| 		"tmp": "0.2.5", | 		"tmp": "0.2.5", | ||||||
| 		"tsc-alias": "1.8.16", | 		"tsc-alias": "1.8.16", | ||||||
| 		"tsconfig-paths": "4.2.0", | 		"tsconfig-paths": "4.2.0", | ||||||
| 		"typeorm": "0.3.26", | 		"typeorm": "0.3.27", | ||||||
| 		"typescript": "5.9.2", | 		"typescript": "5.9.2", | ||||||
| 		"ulid": "2.4.0", | 		"ulid": "2.4.0", | ||||||
| 		"vary": "1.1.2", | 		"vary": "1.1.2", | ||||||
|  | @ -210,7 +210,7 @@ | ||||||
| 		"@types/jsrsasign": "10.5.15", | 		"@types/jsrsasign": "10.5.15", | ||||||
| 		"@types/mime-types": "2.1.4", | 		"@types/mime-types": "2.1.4", | ||||||
| 		"@types/ms": "0.7.34", | 		"@types/ms": "0.7.34", | ||||||
| 		"@types/node": "22.18.1", | 		"@types/node": "22.18.6", | ||||||
| 		"@types/nodemailer": "6.4.19", | 		"@types/nodemailer": "6.4.19", | ||||||
| 		"@types/oauth": "0.9.6", | 		"@types/oauth": "0.9.6", | ||||||
| 		"@types/oauth2orize": "1.11.5", | 		"@types/oauth2orize": "1.11.5", | ||||||
|  | @ -231,8 +231,8 @@ | ||||||
| 		"@types/vary": "1.1.3", | 		"@types/vary": "1.1.3", | ||||||
| 		"@types/web-push": "3.6.4", | 		"@types/web-push": "3.6.4", | ||||||
| 		"@types/ws": "8.18.1", | 		"@types/ws": "8.18.1", | ||||||
| 		"@typescript-eslint/eslint-plugin": "8.42.0", | 		"@typescript-eslint/eslint-plugin": "8.44.0", | ||||||
| 		"@typescript-eslint/parser": "8.42.0", | 		"@typescript-eslint/parser": "8.44.0", | ||||||
| 		"aws-sdk-client-mock": "4.1.0", | 		"aws-sdk-client-mock": "4.1.0", | ||||||
| 		"cross-env": "7.0.3", | 		"cross-env": "7.0.3", | ||||||
| 		"eslint-plugin-import": "2.32.0", | 		"eslint-plugin-import": "2.32.0", | ||||||
|  |  | ||||||
|  | @ -4,11 +4,14 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { defineImageEffectorFx } from '../ImageEffector.js'; | import { defineImageEffectorFx } from '../ImageEffector.js'; | ||||||
|  | import { GLSL_LIB_SNOISE } from '@/utility/webgl.js'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| 
 | 
 | ||||||
| const shader = `#version 300 es
 | const shader = `#version 300 es
 | ||||||
| precision mediump float; | precision mediump float; | ||||||
| 
 | 
 | ||||||
|  | ${GLSL_LIB_SNOISE} | ||||||
|  | 
 | ||||||
| in vec2 in_uv; | in vec2 in_uv; | ||||||
| uniform sampler2D in_texture; | uniform sampler2D in_texture; | ||||||
| uniform vec2 in_resolution; | uniform vec2 in_resolution; | ||||||
|  | @ -22,10 +25,22 @@ out vec4 out_color; | ||||||
| 
 | 
 | ||||||
| void main() { | void main() { | ||||||
| 	vec4 in_color = texture(in_texture, in_uv); | 	vec4 in_color = texture(in_texture, in_uv); | ||||||
| 	float angle = atan(-u_pos.y + (in_uv.y), -u_pos.x + (in_uv.x)); | 	vec2 centeredUv = (in_uv - vec2(0.5, 0.5)); | ||||||
| 	float t = (1.0 + sin(angle * u_frequency)) / 2.0; | 	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; | 	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)); | 
 | ||||||
|  | 	// 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))); | 	float mask = d < u_maskSize ? 0.0 : ((d - u_maskSize) * (1.0 + (u_maskSize * 2.0))); | ||||||
| 	out_color = vec4( | 	out_color = vec4( | ||||||
| 		mix(in_color.r, u_black ? 0.0 : 1.0, t * mask), | 		mix(in_color.r, u_black ? 0.0 : 1.0, t * mask), | ||||||
|  | @ -61,9 +76,9 @@ export const FX_zoomLines = defineImageEffectorFx({ | ||||||
| 		frequency: { | 		frequency: { | ||||||
| 			label: i18n.ts._imageEffector._fxProps.frequency, | 			label: i18n.ts._imageEffector._fxProps.frequency, | ||||||
| 			type: 'number', | 			type: 'number', | ||||||
| 			default: 30.0, | 			default: 5.0, | ||||||
| 			min: 1.0, | 			min: 0.0, | ||||||
| 			max: 200.0, | 			max: 15.0, | ||||||
| 			step: 0.1, | 			step: 0.1, | ||||||
| 		}, | 		}, | ||||||
| 		smoothing: { | 		smoothing: { | ||||||
|  | @ -75,7 +90,7 @@ export const FX_zoomLines = defineImageEffectorFx({ | ||||||
| 		threshold: { | 		threshold: { | ||||||
| 			label: i18n.ts._imageEffector._fxProps.zoomLinesThreshold, | 			label: i18n.ts._imageEffector._fxProps.zoomLinesThreshold, | ||||||
| 			type: 'number', | 			type: 'number', | ||||||
| 			default: 0.2, | 			default: 0.5, | ||||||
| 			min: 0.0, | 			min: 0.0, | ||||||
| 			max: 1.0, | 			max: 1.0, | ||||||
| 			step: 0.01, | 			step: 0.01, | ||||||
|  | @ -95,8 +110,8 @@ export const FX_zoomLines = defineImageEffectorFx({ | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	main: ({ gl, u, params }) => { | 	main: ({ gl, u, params }) => { | ||||||
| 		gl.uniform2f(u.pos, (1.0 + params.x) / 2.0, (1.0 + params.y) / 2.0); | 		gl.uniform2f(u.pos, params.x / 2, params.y / 2); | ||||||
| 		gl.uniform1f(u.frequency, params.frequency); | 		gl.uniform1f(u.frequency, params.frequency * params.frequency); | ||||||
| 		// thresholdの調整が有効な間はsmoothingが利用できない
 | 		// thresholdの調整が有効な間はsmoothingが利用できない
 | ||||||
| 		gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1); | 		gl.uniform1i(u.thresholdEnabled, params.smoothing ? 0 : 1); | ||||||
| 		gl.uniform1f(u.threshold, params.threshold); | 		gl.uniform1f(u.threshold, params.threshold); | ||||||
|  |  | ||||||
|  | @ -38,3 +38,91 @@ export function initShaderProgram(gl: WebGL2RenderingContext, vsSource: string, | ||||||
| 
 | 
 | ||||||
| 	return shaderProgram; | 	return shaderProgram; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export const GLSL_LIB_SNOISE = ` | ||||||
|  | // Description : Array and textureless GLSL 2D/3D/4D simplex
 | ||||||
|  | //               noise functions.
 | ||||||
|  | //      Author : Ian McEwan, Ashima Arts.
 | ||||||
|  | //  Maintainer : stegu
 | ||||||
|  | //     Lastmod : 20201014 (stegu)
 | ||||||
|  | //     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
 | ||||||
|  | //               Distributed under the MIT License. See LICENSE file.
 | ||||||
|  | //               https://github.com/ashima/webgl-noise
 | ||||||
|  | //               https://github.com/stegu/webgl-noise
 | ||||||
|  | 
 | ||||||
|  | vec3 mod289(vec3 x) { | ||||||
|  | 	return x - floor(x * (1.0 / 289.0)) * 289.0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | vec4 mod289(vec4 x) { | ||||||
|  | 	return x - floor(x * (1.0 / 289.0)) * 289.0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | vec4 permute(vec4 x) { | ||||||
|  | 	return mod289(((x * 34.0) + 10.0) * x); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | vec4 taylorInvSqrt(vec4 r) { | ||||||
|  | 	return 1.79284291400159 - 0.85373472095314 * r; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | float snoise(vec3 v) { | ||||||
|  | 	const vec2 C = vec2(1.0/6.0, 1.0/3.0); | ||||||
|  | 	const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); | ||||||
|  | 
 | ||||||
|  | 	vec3 i = floor(v + dot(v, C.yyy)); | ||||||
|  | 	vec3 x0 = v - i + dot(i, C.xxx); | ||||||
|  | 
 | ||||||
|  | 	vec3 g = step(x0.yzx, x0.xyz); | ||||||
|  | 	vec3 l = 1.0 - g; | ||||||
|  | 	vec3 i1 = min(g.xyz, l.zxy); | ||||||
|  | 	vec3 i2 = max(g.xyz, l.zxy); | ||||||
|  | 
 | ||||||
|  | 	vec3 x1 = x0 - i1 + C.xxx; | ||||||
|  | 	vec3 x2 = x0 - i2 + C.yyy; | ||||||
|  | 	vec3 x3 = x0 - D.yyy; | ||||||
|  | 
 | ||||||
|  | 	i = mod289(i); | ||||||
|  | 	vec4 p = permute(permute(permute( | ||||||
|  | 						i.z + vec4(0.0, i1.z, i2.z, 1.0)) | ||||||
|  | 					+ i.y + vec4(0.0, i1.y, i2.y, 1.0)) | ||||||
|  | 					+ i.x + vec4(0.0, i1.x, i2.x, 1.0)); | ||||||
|  | 
 | ||||||
|  | 	float n_ = 0.142857142857; | ||||||
|  | 	vec3 ns = n_ * D.wyz - D.xzx; | ||||||
|  | 
 | ||||||
|  | 	vec4 j = p - 49.0 * floor(p * ns.z * ns.z); | ||||||
|  | 
 | ||||||
|  | 	vec4 x_ = floor(j * ns.z); | ||||||
|  | 	vec4 y_ = floor(j - 7.0 * x_); | ||||||
|  | 
 | ||||||
|  | 	vec4 x = x_ * ns.x + ns.yyyy; | ||||||
|  | 	vec4 y = y_ * ns.x + ns.yyyy; | ||||||
|  | 	vec4 h = 1.0 - abs(x) - abs(y); | ||||||
|  | 
 | ||||||
|  | 	vec4 b0 = vec4(x.xy, y.xy); | ||||||
|  | 	vec4 b1 = vec4(x.zw, y.zw); | ||||||
|  | 
 | ||||||
|  | 	vec4 s0 = floor(b0) * 2.0 + 1.0; | ||||||
|  | 	vec4 s1 = floor(b1) * 2.0 + 1.0; | ||||||
|  | 	vec4 sh = -step(h, vec4(0.0)); | ||||||
|  | 
 | ||||||
|  | 	vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; | ||||||
|  | 	vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; | ||||||
|  | 
 | ||||||
|  | 	vec3 p0 = vec3(a0.xy, h.x); | ||||||
|  | 	vec3 p1 = vec3(a0.zw, h.y); | ||||||
|  | 	vec3 p2 = vec3(a1.xy, h.z); | ||||||
|  | 	vec3 p3 = vec3(a1.zw, h.w); | ||||||
|  | 
 | ||||||
|  | 	vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); | ||||||
|  | 	p0 *= norm.x; | ||||||
|  | 	p1 *= norm.y; | ||||||
|  | 	p2 *= norm.z; | ||||||
|  | 	p3 *= norm.w; | ||||||
|  | 
 | ||||||
|  | 	vec4 m = max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); | ||||||
|  | 	m = m * m; | ||||||
|  | 	return 105.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); | ||||||
|  | } | ||||||
|  | `;
 | ||||||
|  |  | ||||||
							
								
								
									
										1663
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										1663
									
								
								pnpm-lock.yaml
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -15,6 +15,7 @@ | ||||||
| 	dependencyDashboardAutoclose: true, | 	dependencyDashboardAutoclose: true, | ||||||
| 	osvVulnerabilityAlerts: true, | 	osvVulnerabilityAlerts: true, | ||||||
| 	dependencyDashboardOSVVulnerabilitySummary: 'unresolved', | 	dependencyDashboardOSVVulnerabilitySummary: 'unresolved', | ||||||
|  | 	minimumReleaseAge: '7 days', | ||||||
| 	ignoreDeps: [ | 	ignoreDeps: [ | ||||||
| 		// https://github.com/misskey-dev/misskey/pull/15489#issuecomment-2660717458 | 		// https://github.com/misskey-dev/misskey/pull/15489#issuecomment-2660717458 | ||||||
| 		'@typescript/lib-webworker', | 		'@typescript/lib-webworker', | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue