This commit is contained in:
syuilo 2025-09-19 20:55:24 +09:00
parent dc7d50f583
commit 7a17f021d1
6 changed files with 35 additions and 22 deletions

8
locales/index.d.ts vendored
View File

@ -12236,9 +12236,9 @@ export interface Locale extends ILocale {
*/ */
"text": string; "text": string;
/** /**
* () *
*/ */
"accountQr": string; "qr": string;
/** /**
* *
*/ */
@ -12303,6 +12303,10 @@ export interface Locale extends ILocale {
* *
*/ */
"polkadotSubDotDivisions": string; "polkadotSubDotDivisions": string;
/**
* URLになります
*/
"leaveBlankToAccountUrl": string;
}; };
"_imageEffector": { "_imageEffector": {
/** /**

View File

@ -3275,7 +3275,7 @@ _watermarkEditor:
opacity: "不透明度" opacity: "不透明度"
scale: "サイズ" scale: "サイズ"
text: "テキスト" text: "テキスト"
accountQr: "二次元コード (アカウント)" qr: "二次元コード"
position: "位置" position: "位置"
margin: "マージン" margin: "マージン"
type: "タイプ" type: "タイプ"
@ -3292,6 +3292,7 @@ _watermarkEditor:
polkadotSubDotOpacity: "サブドットの不透明度" polkadotSubDotOpacity: "サブドットの不透明度"
polkadotSubDotRadius: "サブドットの大きさ" polkadotSubDotRadius: "サブドットの大きさ"
polkadotSubDotDivisions: "サブドットの数" polkadotSubDotDivisions: "サブドットの数"
leaveBlankToAccountUrl: "空欄にするとアカウントのURLになります"
_imageEffector: _imageEffector:
title: "エフェクト" title: "エフェクト"

View File

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:step="0.01" :step="0.01"
:textConverter="(v) => (v * 100).toFixed(1) + '%'" :textConverter="(v) => (v * 100).toFixed(1) + '%'"
continuousUpdate continuousUpdate
@update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'account-qr' }>).align.margin = v" @update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'text' }>).align.margin = v"
> >
<template #label>{{ i18n.ts._watermarkEditor.margin }}</template> <template #label>{{ i18n.ts._watermarkEditor.margin }}</template>
</MkRange> </MkRange>
@ -85,7 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:step="0.01" :step="0.01"
:textConverter="(v) => (v * 100).toFixed(1) + '%'" :textConverter="(v) => (v * 100).toFixed(1) + '%'"
continuousUpdate continuousUpdate
@update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'account-qr' }>).align.margin = v" @update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'image' }>).align.margin = v"
> >
<template #label>{{ i18n.ts._watermarkEditor.margin }}</template> <template #label>{{ i18n.ts._watermarkEditor.margin }}</template>
</MkRange> </MkRange>
@ -131,7 +131,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</template> </template>
<template v-else-if="layer.type === 'account-qr'"> <template v-else-if="layer.type === 'qr'">
<MkInput v-model="layer.data" debounce>
<template #label>{{ i18n.ts._watermarkEditor.text }}</template>
<template #caption>{{ i18n.ts._watermarkEditor.leaveBlankToAccountUrl }}</template>
</MkInput>
<FormSlot> <FormSlot>
<template #label>{{ i18n.ts._watermarkEditor.position }}</template> <template #label>{{ i18n.ts._watermarkEditor.position }}</template>
<MkPositionSelector <MkPositionSelector
@ -147,7 +152,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:step="0.01" :step="0.01"
:textConverter="(v) => (v * 100).toFixed(1) + '%'" :textConverter="(v) => (v * 100).toFixed(1) + '%'"
continuousUpdate continuousUpdate
@update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'account-qr' }>).align.margin = v" @update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'qr' }>).align.margin = v"
> >
<template #label>{{ i18n.ts._watermarkEditor.margin }}</template> <template #label>{{ i18n.ts._watermarkEditor.margin }}</template>
</MkRange> </MkRange>

View File

@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label> <template #label>
<div v-if="layer.type === 'text'">{{ i18n.ts._watermarkEditor.text }}</div> <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 === 'image'">{{ i18n.ts._watermarkEditor.image }}</div>
<div v-if="layer.type === 'account-qr'">{{ i18n.ts._watermarkEditor.accountQr }}</div> <div v-if="layer.type === 'qr'">{{ i18n.ts._watermarkEditor.qr }}</div>
<div v-if="layer.type === 'stripe'">{{ i18n.ts._watermarkEditor.stripe }}</div> <div v-if="layer.type === 'stripe'">{{ i18n.ts._watermarkEditor.stripe }}</div>
<div v-if="layer.type === 'polkadot'">{{ i18n.ts._watermarkEditor.polkadot }}</div> <div v-if="layer.type === 'polkadot'">{{ i18n.ts._watermarkEditor.polkadot }}</div>
<div v-if="layer.type === 'checker'">{{ i18n.ts._watermarkEditor.checker }}</div> <div v-if="layer.type === 'checker'">{{ i18n.ts._watermarkEditor.checker }}</div>
@ -108,10 +108,11 @@ function createImageLayer(): WatermarkPreset['layers'][number] {
}; };
} }
function createAccountQrLayer(): WatermarkPreset['layers'][number] { function createQrLayer(): WatermarkPreset['layers'][number] {
return { return {
id: genId(), id: genId(),
type: 'account-qr', type: 'qr',
data: '',
align: { x: 'right', y: 'bottom', margin: 0 }, align: { x: 'right', y: 'bottom', margin: 0 },
scale: 0.3, scale: 0.3,
opacity: 1, opacity: 1,
@ -317,9 +318,9 @@ function addLayer(ev: MouseEvent) {
preset.layers.push(createImageLayer()); preset.layers.push(createImageLayer());
}, },
}, { }, {
text: i18n.ts._watermarkEditor.accountQr, text: i18n.ts._watermarkEditor.qr,
action: () => { action: () => {
preset.layers.push(createAccountQrLayer()); preset.layers.push(createQrLayer());
}, },
}, { }, {
text: i18n.ts._watermarkEditor.stripe, text: i18n.ts._watermarkEditor.stripe,

View File

@ -67,7 +67,7 @@ interface TextureParamDef extends CommonParamDef {
} | { } | {
type: 'url'; url: string | null; type: 'url'; url: string | null;
} | { } | {
type: 'account-qr'; type: 'qr'; data: string | null;
} | null; } | null;
}; };
@ -337,7 +337,7 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
const texture = const texture =
v.type === 'text' ? await createTextureFromText(this.gl, v.text) : v.type === 'text' ? await createTextureFromText(this.gl, v.text) :
v.type === 'url' ? await createTextureFromUrl(this.gl, v.url) : v.type === 'url' ? await createTextureFromUrl(this.gl, v.url) :
v.type === 'account-qr' ? await createTextureFromAccountQr(this.gl) : v.type === 'qr' ? await createTextureFromQr(this.gl, { data: v.data }) :
null; null;
if (texture == null) continue; if (texture == null) continue;
@ -369,7 +369,7 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
return ( return (
v.type === 'text' ? `text:${v.text}` : v.type === 'text' ? `text:${v.text}` :
v.type === 'url' ? `url:${v.url}` : v.type === 'url' ? `url:${v.url}` :
v.type === 'account-qr' ? 'account-qr' : v.type === 'qr' ? `qr:${v.data}` :
'' ''
); );
} }
@ -487,7 +487,7 @@ async function createTextureFromText(gl: WebGL2RenderingContext, text: string |
return info; return info;
} }
async function createTextureFromAccountQr(gl: WebGL2RenderingContext, resolution = 512): Promise<{ texture: WebGLTexture, width: number, height: number } | null> { async function createTextureFromQr(gl: WebGL2RenderingContext, options: { data: string | null }, resolution = 512): Promise<{ texture: WebGLTexture, width: number, height: number } | null> {
const $i = ensureSignin(); const $i = ensureSignin();
const qrCodeInstance = new QRCodeStyling({ const qrCodeInstance = new QRCodeStyling({
@ -495,7 +495,7 @@ async function createTextureFromAccountQr(gl: WebGL2RenderingContext, resolution
height: resolution, height: resolution,
margin: 42, margin: 42,
type: 'canvas', type: 'canvas',
data: `${url}/users/${$i.id}`, data: options.data == null || options.data === '' ? `${url}/users/${$i.id}` : options.data,
image: $i.avatarUrl, image: $i.avatarUrl,
qrOptions: { qrOptions: {
typeNumber: 0, typeNumber: 0,
@ -522,12 +522,12 @@ async function createTextureFromAccountQr(gl: WebGL2RenderingContext, resolution
const blob = await qrCodeInstance.getRawData('png') as Blob | null; const blob = await qrCodeInstance.getRawData('png') as Blob | null;
if (blob == null) return null; if (blob == null) return null;
const data = await window.createImageBitmap(blob); const image = await window.createImageBitmap(blob);
const texture = createTexture(gl); const texture = createTexture(gl);
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, resolution, resolution, 0, gl.RGBA, gl.UNSIGNED_BYTE, data); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, resolution, resolution, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
return { return {

View File

@ -44,7 +44,8 @@ export type WatermarkPreset = {
opacity: number; opacity: number;
} | { } | {
id: string; id: string;
type: 'account-qr'; type: 'qr';
data: string;
scale: number; scale: number;
align: Align; align: Align;
opacity: number; opacity: number;
@ -133,7 +134,7 @@ export class WatermarkRenderer {
}, },
}, },
}; };
} else if (layer.type === 'account-qr') { } else if (layer.type === 'qr') {
return { return {
fxId: 'watermarkPlacement', fxId: 'watermarkPlacement',
id: layer.id, id: layer.id,
@ -145,7 +146,8 @@ export class WatermarkRenderer {
opacity: layer.opacity, opacity: layer.opacity,
cover: false, cover: false,
watermark: { watermark: {
type: 'account-qr', type: 'qr',
data: layer.data,
}, },
}, },
}; };