wip
This commit is contained in:
parent
0050b873c6
commit
400c7cc408
|
|
@ -5618,6 +5618,10 @@ export interface Locale extends ILocale {
|
||||||
* ラベルの幅
|
* ラベルの幅
|
||||||
*/
|
*/
|
||||||
"labelThickness": string;
|
"labelThickness": string;
|
||||||
|
/**
|
||||||
|
* ラベルのスケール
|
||||||
|
*/
|
||||||
|
"labelScale": string;
|
||||||
/**
|
/**
|
||||||
* 中央揃え
|
* 中央揃え
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1401,6 +1401,7 @@ _imageLabelEditor:
|
||||||
title: "ラベルの編集"
|
title: "ラベルの編集"
|
||||||
frameThickness: "フレームの幅"
|
frameThickness: "フレームの幅"
|
||||||
labelThickness: "ラベルの幅"
|
labelThickness: "ラベルの幅"
|
||||||
|
labelScale: "ラベルのスケール"
|
||||||
centered: "中央揃え"
|
centered: "中央揃え"
|
||||||
captionMain: "キャプション(大)"
|
captionMain: "キャプション(大)"
|
||||||
captionSub: "キャプション(小)"
|
captionSub: "キャプション(小)"
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.frameThickness }}</template>
|
<template #label>{{ i18n.ts._imageLabelEditor.frameThickness }}</template>
|
||||||
</MkRange>
|
</MkRange>
|
||||||
|
|
||||||
<MkRange v-model="frame.labelThickness" :min="0.1" :max="0.3" :step="0.01" :continuousUpdate="true">
|
<MkRange v-model="frame.labelThickness" :min="0.01" :max="0.5" :step="0.01" :continuousUpdate="true">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.labelThickness }}</template>
|
<template #label>{{ i18n.ts._imageLabelEditor.labelThickness }}</template>
|
||||||
</MkRange>
|
</MkRange>
|
||||||
|
|
||||||
|
<MkRange v-model="frame.labelScale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
||||||
|
<template #label>{{ i18n.ts._imageLabelEditor.labelScale }}</template>
|
||||||
|
</MkRange>
|
||||||
|
|
||||||
<MkSwitch v-model="frame.centered">
|
<MkSwitch v-model="frame.centered">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.centered }}</template>
|
<template #label>{{ i18n.ts._imageLabelEditor.centered }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
@ -74,6 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick } from 'vue';
|
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick } from 'vue';
|
||||||
import ExifReader from 'exifreader';
|
import ExifReader from 'exifreader';
|
||||||
|
import { throttle } from 'throttle-debounce';
|
||||||
import type { ImageLabelParams } from '@/utility/image-label-renderer.js';
|
import type { ImageLabelParams } from '@/utility/image-label-renderer.js';
|
||||||
import { ImageLabelRenderer } from '@/utility/image-label-renderer.js';
|
import { ImageLabelRenderer } from '@/utility/image-label-renderer.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
@ -114,6 +119,7 @@ const frame = reactive<ImageLabelParams>(deepClone(props.frame) ?? {
|
||||||
style: 'frame',
|
style: 'frame',
|
||||||
frameThickness: 0.05,
|
frameThickness: 0.05,
|
||||||
labelThickness: 0.2,
|
labelThickness: 0.2,
|
||||||
|
labelScale: 1.0,
|
||||||
title: 'Untitled by @syuilo',
|
title: 'Untitled by @syuilo',
|
||||||
text: '{mm}mm f/{f} {s}s ISO{iso}',
|
text: '{mm}mm f/{f} {s}s ISO{iso}',
|
||||||
centered: false,
|
centered: false,
|
||||||
|
|
@ -132,10 +138,14 @@ async function cancel() {
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(frame, async (newValue, oldValue) => {
|
const updateThrottled = throttle(100, () => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.update(frame);
|
renderer.update(frame);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(frame, async (newValue, oldValue) => {
|
||||||
|
updateThrottled();
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
const canvasEl = useTemplateRef('canvasEl');
|
const canvasEl = useTemplateRef('canvasEl');
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ uniform sampler2D in_texture;
|
||||||
uniform vec2 in_resolution;
|
uniform vec2 in_resolution;
|
||||||
uniform sampler2D u_image;
|
uniform sampler2D u_image;
|
||||||
uniform sampler2D u_label;
|
uniform sampler2D u_label;
|
||||||
uniform vec2 u_labelResolution;
|
uniform float u_paddingTop;
|
||||||
uniform bool u_labelEnabled;
|
uniform float u_paddingBottom;
|
||||||
uniform float u_imageMarginX;
|
uniform float u_paddingLeft;
|
||||||
uniform float u_imageMarginY;
|
uniform float u_paddingRight;
|
||||||
out vec4 out_color;
|
out vec4 out_color;
|
||||||
|
|
||||||
float remap(float value, float inputMin, float inputMax, float outputMin, float outputMax) {
|
float remap(float value, float inputMin, float inputMax, float outputMin, float outputMax) {
|
||||||
|
|
@ -22,19 +22,20 @@ float remap(float value, float inputMin, float inputMax, float outputMin, float
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float labelRatio = u_labelEnabled ? (u_labelResolution.y / in_resolution.y) : 0.0;
|
|
||||||
|
|
||||||
vec4 image_color = texture(u_image, vec2(
|
vec4 image_color = texture(u_image, vec2(
|
||||||
remap(in_uv.x, u_imageMarginX, 1.0 - u_imageMarginX, 0.0, 1.0),
|
remap(in_uv.x, u_paddingLeft, 1.0 - u_paddingRight, 0.0, 1.0),
|
||||||
remap(in_uv.y, u_imageMarginY, 1.0 - labelRatio, 0.0, 1.0)
|
remap(in_uv.y, u_paddingTop, 1.0 - u_paddingBottom, 0.0, 1.0)
|
||||||
));
|
));
|
||||||
|
|
||||||
vec4 label_color = texture(u_label, (in_uv - vec2(0.0, 1.0 - labelRatio)) / vec2(1.0, labelRatio));
|
vec4 label_color = texture(u_label, vec2(
|
||||||
|
in_uv.x,
|
||||||
|
remap(in_uv.y, 1.0 - u_paddingBottom, 1.0, 0.0, 1.0)
|
||||||
|
));
|
||||||
|
|
||||||
if (in_uv.y > (1.0 - labelRatio)) {
|
if (in_uv.y > (1.0 - u_paddingBottom)) {
|
||||||
out_color = label_color;
|
out_color = label_color;
|
||||||
} else {
|
} else {
|
||||||
if (in_uv.y > u_imageMarginY && in_uv.x > u_imageMarginX && in_uv.x < (1.0 - u_imageMarginX)) {
|
if (in_uv.y > u_paddingTop && in_uv.x > u_paddingLeft && in_uv.x < (1.0 - u_paddingRight)) {
|
||||||
out_color = image_color;
|
out_color = image_color;
|
||||||
} else {
|
} else {
|
||||||
out_color = vec4(1.0, 1.0, 1.0, 1.0);
|
out_color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export const FX_label = defineImageEffectorFx({
|
||||||
id: 'label',
|
id: 'label',
|
||||||
name: '(internal)',
|
name: '(internal)',
|
||||||
shader,
|
shader,
|
||||||
uniforms: ['image', 'label', 'labelResolution', 'labelEnabled', 'imageMarginX', 'imageMarginY'] as const,
|
uniforms: ['image', 'label', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'] as const,
|
||||||
params: {
|
params: {
|
||||||
image: {
|
image: {
|
||||||
type: 'textureRef',
|
type: 'textureRef',
|
||||||
|
|
@ -20,15 +20,27 @@ export const FX_label = defineImageEffectorFx({
|
||||||
type: 'textureRef',
|
type: 'textureRef',
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
imageMarginX: {
|
paddingTop: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: 0.05,
|
default: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
imageMarginY: {
|
paddingBottom: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: 0.05,
|
default: 0,
|
||||||
|
max: 1,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
paddingLeft: {
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
max: 1,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
paddingRight: {
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
max: 1,
|
max: 1,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
@ -39,19 +51,16 @@ export const FX_label = defineImageEffectorFx({
|
||||||
gl.bindTexture(gl.TEXTURE_2D, image.texture);
|
gl.bindTexture(gl.TEXTURE_2D, image.texture);
|
||||||
gl.uniform1i(u.image, 1);
|
gl.uniform1i(u.image, 1);
|
||||||
|
|
||||||
gl.uniform1f(u.imageMarginX, params.imageMarginX);
|
gl.uniform1f(u.paddingTop, params.paddingTop);
|
||||||
gl.uniform1f(u.imageMarginY, params.imageMarginY);
|
gl.uniform1f(u.paddingBottom, params.paddingBottom);
|
||||||
|
gl.uniform1f(u.paddingLeft, params.paddingLeft);
|
||||||
|
gl.uniform1f(u.paddingRight, params.paddingRight);
|
||||||
|
|
||||||
const label = textures.label;
|
const label = textures.label;
|
||||||
if (label) {
|
if (label) {
|
||||||
gl.activeTexture(gl.TEXTURE2);
|
gl.activeTexture(gl.TEXTURE2);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, label.texture);
|
gl.bindTexture(gl.TEXTURE_2D, label.texture);
|
||||||
|
|
||||||
gl.uniform1i(u.label, 2);
|
gl.uniform1i(u.label, 2);
|
||||||
gl.uniform2f(u.labelResolution, label.width, label.height);
|
|
||||||
gl.uniform1i(u.labelEnabled, 1);
|
|
||||||
} else {
|
|
||||||
gl.uniform1i(u.labelEnabled, 0);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export type ImageLabelParams = {
|
||||||
style: 'frame' | 'frameLess';
|
style: 'frame' | 'frameLess';
|
||||||
frameThickness: number;
|
frameThickness: number;
|
||||||
labelThickness: number;
|
labelThickness: number;
|
||||||
|
labelScale: number;
|
||||||
title: string;
|
title: string;
|
||||||
text: string;
|
text: string;
|
||||||
centered: boolean;
|
centered: boolean;
|
||||||
|
|
@ -96,11 +97,12 @@ export class ImageLabelRenderer {
|
||||||
const labelCanvasCtx = window.document.createElement('canvas').getContext('2d')!;
|
const labelCanvasCtx = window.document.createElement('canvas').getContext('2d')!;
|
||||||
labelCanvasCtx.canvas.width = renderWidth;
|
labelCanvasCtx.canvas.width = renderWidth;
|
||||||
labelCanvasCtx.canvas.height = paddingBottom;
|
labelCanvasCtx.canvas.height = paddingBottom;
|
||||||
const fontSize = labelCanvasCtx.canvas.height / 6;
|
const scaleBase = imageAreaH * params.labelScale;
|
||||||
|
const fontSize = scaleBase / 30;
|
||||||
const textsMarginLeft = Math.max(fontSize * 2, paddingLeft);
|
const textsMarginLeft = Math.max(fontSize * 2, paddingLeft);
|
||||||
const textsMarginRight = textsMarginLeft;
|
const textsMarginRight = textsMarginLeft;
|
||||||
const withQrCode = params.withQrCode;
|
const withQrCode = params.withQrCode;
|
||||||
const qrSize = labelCanvasCtx.canvas.height * 0.6;
|
const qrSize = scaleBase * 0.1;
|
||||||
const qrMarginRight = Math.max((labelCanvasCtx.canvas.height - qrSize) / 2, paddingRight);
|
const qrMarginRight = Math.max((labelCanvasCtx.canvas.height - qrSize) / 2, paddingRight);
|
||||||
|
|
||||||
labelCanvasCtx.fillStyle = '#ffffff';
|
labelCanvasCtx.fillStyle = '#ffffff';
|
||||||
|
|
@ -135,49 +137,53 @@ export class ImageLabelRenderer {
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
if (withQrCode) {
|
if (withQrCode) {
|
||||||
const qrCodeInstance = new QRCodeStyling({
|
try {
|
||||||
width: labelCanvasCtx.canvas.height,
|
const qrCodeInstance = new QRCodeStyling({
|
||||||
height: labelCanvasCtx.canvas.height,
|
width: labelCanvasCtx.canvas.height,
|
||||||
margin: 0,
|
height: labelCanvasCtx.canvas.height,
|
||||||
type: 'canvas',
|
margin: 0,
|
||||||
data: `${url}/users/${$i.id}`,
|
type: 'canvas',
|
||||||
//image: $i.avatarUrl,
|
data: `${url}/users/${$i.id}`,
|
||||||
qrOptions: {
|
//image: $i.avatarUrl,
|
||||||
typeNumber: 0,
|
qrOptions: {
|
||||||
mode: 'Byte',
|
typeNumber: 0,
|
||||||
errorCorrectionLevel: 'H',
|
mode: 'Byte',
|
||||||
},
|
errorCorrectionLevel: 'H',
|
||||||
imageOptions: {
|
},
|
||||||
hideBackgroundDots: true,
|
imageOptions: {
|
||||||
imageSize: 0.3,
|
hideBackgroundDots: true,
|
||||||
margin: 16,
|
imageSize: 0.3,
|
||||||
crossOrigin: 'anonymous',
|
margin: 16,
|
||||||
},
|
crossOrigin: 'anonymous',
|
||||||
dotsOptions: {
|
},
|
||||||
type: 'dots',
|
dotsOptions: {
|
||||||
roundSize: false,
|
type: 'dots',
|
||||||
},
|
roundSize: false,
|
||||||
cornersDotOptions: {
|
},
|
||||||
type: 'dot',
|
cornersDotOptions: {
|
||||||
},
|
type: 'dot',
|
||||||
cornersSquareOptions: {
|
},
|
||||||
type: 'extra-rounded',
|
cornersSquareOptions: {
|
||||||
},
|
type: 'extra-rounded',
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const blob = await qrCodeInstance.getRawData('png') as Blob | null;
|
const blob = await qrCodeInstance.getRawData('png') as Blob | null;
|
||||||
if (blob == null) throw new Error('Failed to generate QR code');
|
if (blob == null) throw new Error('Failed to generate QR code');
|
||||||
|
|
||||||
const qrImageBitmap = await window.createImageBitmap(blob);
|
const qrImageBitmap = await window.createImageBitmap(blob);
|
||||||
|
|
||||||
labelCanvasCtx.drawImage(
|
labelCanvasCtx.drawImage(
|
||||||
qrImageBitmap,
|
qrImageBitmap,
|
||||||
labelCanvasCtx.canvas.width - qrSize - qrMarginRight,
|
labelCanvasCtx.canvas.width - qrSize - qrMarginRight,
|
||||||
(labelCanvasCtx.canvas.height - qrSize) / 2,
|
(labelCanvasCtx.canvas.height - qrSize) / 2,
|
||||||
qrSize,
|
qrSize,
|
||||||
qrSize,
|
qrSize,
|
||||||
);
|
);
|
||||||
qrImageBitmap.close();
|
qrImageBitmap.close();
|
||||||
|
} catch (err) {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = labelCanvasCtx.getImageData(0, 0, labelCanvasCtx.canvas.width, labelCanvasCtx.canvas.height);
|
const data = labelCanvasCtx.getImageData(0, 0, labelCanvasCtx.canvas.width, labelCanvasCtx.canvas.height);
|
||||||
|
|
@ -192,8 +198,10 @@ export class ImageLabelRenderer {
|
||||||
params: {
|
params: {
|
||||||
image: 'image',
|
image: 'image',
|
||||||
label: 'label',
|
label: 'label',
|
||||||
imageMarginX: paddingLeft / renderWidth,
|
paddingLeft: paddingLeft / renderWidth,
|
||||||
imageMarginY: paddingTop / renderHeight,
|
paddingRight: paddingRight / renderWidth,
|
||||||
|
paddingTop: paddingTop / renderHeight,
|
||||||
|
paddingBottom: paddingBottom / renderHeight,
|
||||||
},
|
},
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue