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