wip
This commit is contained in:
parent
d550e42b0d
commit
8847a4aa6e
|
|
@ -5605,7 +5605,7 @@ export interface Locale extends ILocale {
|
||||||
* 技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。
|
* 技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。
|
||||||
*/
|
*/
|
||||||
"deviceInfoDescription": string;
|
"deviceInfoDescription": string;
|
||||||
"_imageLabelEditor": {
|
"_imageFrameEditor": {
|
||||||
/**
|
/**
|
||||||
* ラベルの編集
|
* ラベルの編集
|
||||||
*/
|
*/
|
||||||
|
|
@ -5613,7 +5613,7 @@ export interface Locale extends ILocale {
|
||||||
/**
|
/**
|
||||||
* フレームの幅
|
* フレームの幅
|
||||||
*/
|
*/
|
||||||
"frameThickness": string;
|
"borderThickness": string;
|
||||||
/**
|
/**
|
||||||
* ラベルの幅
|
* ラベルの幅
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1396,10 +1396,11 @@ scheduled: "予約"
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
deviceInfo: "デバイス情報"
|
deviceInfo: "デバイス情報"
|
||||||
deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。"
|
deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。"
|
||||||
|
frame: "フレーム"
|
||||||
|
|
||||||
_imageLabelEditor:
|
_imageFrameEditor:
|
||||||
title: "ラベルの編集"
|
title: "フレームの編集"
|
||||||
frameThickness: "フレームの幅"
|
borderThickness: "フチの幅"
|
||||||
labelThickness: "ラベルの幅"
|
labelThickness: "ラベルの幅"
|
||||||
labelScale: "ラベルのスケール"
|
labelScale: "ラベルのスケール"
|
||||||
centered: "中央揃え"
|
centered: "中央揃え"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@ok="save()"
|
@ok="save()"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._imageLabelEditor.title }}</template>
|
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._imageFrameEditor.title }}</template>
|
||||||
|
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
|
|
@ -31,36 +31,36 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.controls">
|
<div :class="$style.controls">
|
||||||
<div class="_spacer _gaps">
|
<div class="_spacer _gaps">
|
||||||
<MkRange v-model="frame.frameThickness" :min="0" :max="0.2" :step="0.01" :continuousUpdate="true">
|
<MkRange v-model="frame.borderThickness" :min="0" :max="0.2" :step="0.01" :continuousUpdate="true">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.frameThickness }}</template>
|
<template #label>{{ i18n.ts._imageFrameEditor.borderThickness }}</template>
|
||||||
</MkRange>
|
</MkRange>
|
||||||
|
|
||||||
<MkRange v-model="frame.labelThickness" :min="0.01" :max="0.5" :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._imageFrameEditor.labelThickness }}</template>
|
||||||
</MkRange>
|
</MkRange>
|
||||||
|
|
||||||
<MkRange v-model="frame.labelScale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
<MkRange v-model="frame.labelScale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.labelScale }}</template>
|
<template #label>{{ i18n.ts._imageFrameEditor.labelScale }}</template>
|
||||||
</MkRange>
|
</MkRange>
|
||||||
|
|
||||||
<MkSwitch v-model="frame.centered">
|
<MkSwitch v-model="frame.centered">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.centered }}</template>
|
<template #label>{{ i18n.ts._imageFrameEditor.centered }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
<MkInput v-model="frame.title">
|
<MkInput v-model="frame.title">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.captionMain }}</template>
|
<template #label>{{ i18n.ts._imageFrameEditor.captionMain }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkTextarea v-model="frame.text">
|
<MkTextarea v-model="frame.text">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.captionSub }}</template>
|
<template #label>{{ i18n.ts._imageFrameEditor.captionSub }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
<MkSwitch v-model="frame.withQrCode">
|
<MkSwitch v-model="frame.withQrCode">
|
||||||
<template #label>{{ i18n.ts._imageLabelEditor.withQrCode }}</template>
|
<template #label>{{ i18n.ts._imageFrameEditor.withQrCode }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
||||||
<MkInfo>
|
<MkInfo>
|
||||||
<div>{{ i18n.ts._imageLabelEditor.availableVariables }}:</div>
|
<div>{{ i18n.ts._imageFrameEditor.availableVariables }}:</div>
|
||||||
<div><code class="_selectableAtomic">{date}</code> - 撮影日時</div>
|
<div><code class="_selectableAtomic">{date}</code> - 撮影日時</div>
|
||||||
<div><code class="_selectableAtomic">{model}</code> - カメラモデル</div>
|
<div><code class="_selectableAtomic">{model}</code> - カメラモデル</div>
|
||||||
<div><code class="_selectableAtomic">{lensModel}</code> - レンズモデル</div>
|
<div><code class="_selectableAtomic">{lensModel}</code> - レンズモデル</div>
|
||||||
|
|
@ -80,8 +80,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
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 { throttle } from 'throttle-debounce';
|
||||||
import type { ImageLabelParams } from '@/utility/image-label-renderer.js';
|
import type { ImageFrameParams } from '@/utility/image-frame-renderer.js';
|
||||||
import { ImageLabelRenderer } from '@/utility/image-label-renderer.js';
|
import { ImageFrameRenderer } from '@/utility/image-frame-renderer.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
|
@ -112,13 +112,12 @@ const EXIF_MOCK = {
|
||||||
} satisfies ExifReader.Tags;
|
} satisfies ExifReader.Tags;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
frame?: ImageLabelParams | null;
|
frame?: ImageFrameParams | null;
|
||||||
image?: File | null;
|
image?: File | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const frame = reactive<ImageLabelParams>(deepClone(props.frame) ?? {
|
const frame = reactive<ImageFrameParams>(deepClone(props.frame) ?? {
|
||||||
style: 'frame',
|
borderThickness: 0.05,
|
||||||
frameThickness: 0.05,
|
|
||||||
labelThickness: 0.2,
|
labelThickness: 0.2,
|
||||||
labelScale: 1.0,
|
labelScale: 1.0,
|
||||||
title: 'Untitled by @syuilo',
|
title: 'Untitled by @syuilo',
|
||||||
|
|
@ -128,7 +127,7 @@ const frame = reactive<ImageLabelParams>(deepClone(props.frame) ?? {
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'ok', frame: ImageLabelParams): void;
|
(ev: 'ok', frame: ImageFrameParams): void;
|
||||||
(ev: 'cancel'): void;
|
(ev: 'cancel'): void;
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -187,21 +186,21 @@ async function choiceImage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let renderer: ImageLabelRenderer | null = null;
|
let renderer: ImageFrameRenderer | null = null;
|
||||||
let imageBitmap: ImageBitmap | null = null;
|
let imageBitmap: ImageBitmap | null = null;
|
||||||
|
|
||||||
async function initRenderer() {
|
async function initRenderer() {
|
||||||
if (canvasEl.value == null) return;
|
if (canvasEl.value == null) return;
|
||||||
|
|
||||||
if (sampleImageType.value === '3_2') {
|
if (sampleImageType.value === '3_2') {
|
||||||
renderer = new ImageLabelRenderer({
|
renderer = new ImageFrameRenderer({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
image: sampleImage_3_2,
|
image: sampleImage_3_2,
|
||||||
exif: EXIF_MOCK,
|
exif: EXIF_MOCK,
|
||||||
renderAsPreview: true,
|
renderAsPreview: true,
|
||||||
});
|
});
|
||||||
} else if (sampleImageType.value === '2_3') {
|
} else if (sampleImageType.value === '2_3') {
|
||||||
renderer = new ImageLabelRenderer({
|
renderer = new ImageFrameRenderer({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
image: sampleImage_2_3,
|
image: sampleImage_2_3,
|
||||||
exif: EXIF_MOCK,
|
exif: EXIF_MOCK,
|
||||||
|
|
@ -212,7 +211,7 @@ async function initRenderer() {
|
||||||
|
|
||||||
const exif = ExifReader.load(await imageFile.arrayBuffer());
|
const exif = ExifReader.load(await imageFile.arrayBuffer());
|
||||||
|
|
||||||
renderer = new ImageLabelRenderer({
|
renderer = new ImageFrameRenderer({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
image: imageBitmap,
|
image: imageBitmap,
|
||||||
exif: exif,
|
exif: exif,
|
||||||
|
|
@ -18,7 +18,7 @@ import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { WatermarkRenderer } from '@/utility/watermark.js';
|
import { WatermarkRenderer } from '@/utility/watermark.js';
|
||||||
import { ImageLabelRenderer } from '@/utility/image-label-renderer.js';
|
import { ImageFrameRenderer } from '@/utility/image-frame-renderer.js';
|
||||||
|
|
||||||
export type UploaderFeatures = {
|
export type UploaderFeatures = {
|
||||||
imageEditing?: boolean;
|
imageEditing?: boolean;
|
||||||
|
|
@ -575,7 +575,7 @@ export function useUploader(options: {
|
||||||
|
|
||||||
const exif = await ExifReader.load(await item.file.arrayBuffer());
|
const exif = await ExifReader.load(await item.file.arrayBuffer());
|
||||||
|
|
||||||
const labelRenderer = new ImageLabelRenderer({
|
const labelRenderer = new ImageFrameRenderer({
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
image: await window.createImageBitmap(preprocessedFile),
|
image: await window.createImageBitmap(preprocessedFile),
|
||||||
exif,
|
exif,
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<SearchMarker :keywords="['label', 'frame', 'credit', 'metadata']">
|
<SearchMarker :keywords="['label', 'frame', 'credit', 'metadata']">
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #icon><i class="ti ti-photo"></i></template>
|
<template #icon><i class="ti ti-photo"></i></template>
|
||||||
<template #label><SearchLabel>{{ i18n.ts.label }}</SearchLabel></template>
|
<template #label><SearchLabel>{{ i18n.ts.frame }}</SearchLabel></template>
|
||||||
|
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
|
|
@ -141,7 +141,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
/>
|
/>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<MkButton iconOnly rounded style="margin: 0 auto;" @click="addImageLabelPreset"><i class="ti ti-plus"></i></MkButton>
|
<MkButton iconOnly rounded style="margin: 0 auto;" @click="addImageFramePreset"><i class="ti ti-plus"></i></MkButton>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<SearchMarker :keywords="['sync', 'watermark', 'preset', 'devices']">
|
<SearchMarker :keywords="['sync', 'watermark', 'preset', 'devices']">
|
||||||
|
|
@ -342,8 +342,8 @@ function onDeleteWatermarkPreset(id: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addImageLabelPreset() {
|
async function addImageFramePreset() {
|
||||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageLabelEditorDialog.vue').then(x => x.default), {
|
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), {
|
||||||
}, {
|
}, {
|
||||||
ok: (preset: any) => {
|
ok: (preset: any) => {
|
||||||
//prefer.commit('imageLabelPresets', [...prefer.s.imageLabelPresets, preset]);
|
//prefer.commit('imageLabelPresets', [...prefer.s.imageLabelPresets, preset]);
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
import { defineImageEffectorFx } from '../ImageEffector.js';
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
import shader from './label.glsl';
|
import shader from './label.glsl';
|
||||||
|
|
||||||
export const FX_label = defineImageEffectorFx({
|
export const FX_frame = defineImageEffectorFx({
|
||||||
id: 'label',
|
id: 'frame',
|
||||||
name: '(internal)',
|
name: '(internal)',
|
||||||
shader,
|
shader,
|
||||||
uniforms: ['image', 'label', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'] as const,
|
uniforms: ['image', 'label', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'] as const,
|
||||||
|
|
@ -6,18 +6,17 @@
|
||||||
import QRCodeStyling from 'qr-code-styling';
|
import QRCodeStyling from 'qr-code-styling';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import ExifReader from 'exifreader';
|
import ExifReader from 'exifreader';
|
||||||
import { FX_label } from './image-effector/fxs/label.js';
|
import { FX_frame } from './image-effector/fxs/frame.js';
|
||||||
import type { ImageEffectorFx, ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
import type { ImageEffectorFx, ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
|
|
||||||
const FXS = [
|
const FXS = [
|
||||||
FX_label,
|
FX_frame,
|
||||||
] as const satisfies ImageEffectorFx<string, any>[];
|
] as const satisfies ImageEffectorFx<string, any>[];
|
||||||
|
|
||||||
export type ImageLabelParams = {
|
export type ImageFrameParams = {
|
||||||
style: 'frame' | 'frameLess';
|
borderThickness: number;
|
||||||
frameThickness: number;
|
|
||||||
labelThickness: number;
|
labelThickness: number;
|
||||||
labelScale: number;
|
labelScale: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -29,7 +28,7 @@ export type ImageLabelParams = {
|
||||||
borderRadius: number; // TODO
|
borderRadius: number; // TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ImageLabelRenderer {
|
export class ImageFrameRenderer {
|
||||||
private effector: ImageEffector<typeof FXS>;
|
private effector: ImageEffector<typeof FXS>;
|
||||||
private image: HTMLImageElement | ImageBitmap;
|
private image: HTMLImageElement | ImageBitmap;
|
||||||
private exif: ExifReader.Tags;
|
private exif: ExifReader.Tags;
|
||||||
|
|
@ -74,7 +73,7 @@ export class ImageLabelRenderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAndRender(params: ImageLabelParams): Promise<void> {
|
public async updateAndRender(params: ImageFrameParams): Promise<void> {
|
||||||
let imageAreaW = this.image.width;
|
let imageAreaW = this.image.width;
|
||||||
let imageAreaH = this.image.height;
|
let imageAreaH = this.image.height;
|
||||||
|
|
||||||
|
|
@ -89,9 +88,9 @@ export class ImageLabelRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const paddingTop = Math.floor(imageAreaH * params.frameThickness);
|
const paddingTop = Math.floor(imageAreaH * params.borderThickness);
|
||||||
const paddingLeft = Math.floor(imageAreaH * params.frameThickness);
|
const paddingLeft = Math.floor(imageAreaH * params.borderThickness);
|
||||||
const paddingRight = Math.floor(imageAreaH * params.frameThickness);
|
const paddingRight = Math.floor(imageAreaH * params.borderThickness);
|
||||||
const paddingBottom = Math.floor(imageAreaH * params.labelThickness);
|
const paddingBottom = Math.floor(imageAreaH * params.labelThickness);
|
||||||
const renderWidth = imageAreaW + paddingLeft + paddingRight;
|
const renderWidth = imageAreaW + paddingLeft + paddingRight;
|
||||||
const renderHeight = imageAreaH + paddingTop + paddingBottom;
|
const renderHeight = imageAreaH + paddingTop + paddingBottom;
|
||||||
|
|
@ -196,7 +195,7 @@ export class ImageLabelRenderer {
|
||||||
this.effector.changeResolution(renderWidth, renderHeight);
|
this.effector.changeResolution(renderWidth, renderHeight);
|
||||||
|
|
||||||
await this.effector.setLayersAndRender([{
|
await this.effector.setLayersAndRender([{
|
||||||
fxId: 'label',
|
fxId: 'frame',
|
||||||
id: 'a',
|
id: 'a',
|
||||||
params: {
|
params: {
|
||||||
image: 'image',
|
image: 'image',
|
||||||
Loading…
Reference in New Issue