mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-28 18:48:31 +01:00
wip
This commit is contained in:
parent
4fc3916219
commit
32ee4d8c7a
3 changed files with 116 additions and 19 deletions
|
@ -9,8 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:width="1000"
|
:width="1000"
|
||||||
:height="600"
|
:height="600"
|
||||||
:scroll="false"
|
:scroll="false"
|
||||||
:withOkButton="false"
|
:withOkButton="true"
|
||||||
@close="cancel()"
|
@close="cancel()"
|
||||||
|
@ok="save()"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header><i class="ti ti-ripple"></i> {{ i18n.ts.watermark }}</template>
|
<template #header><i class="ti ti-ripple"></i> {{ i18n.ts.watermark }}</template>
|
||||||
|
@ -56,13 +57,15 @@ function cancel() {
|
||||||
dialogEl.value?.close();
|
dialogEl.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function save() {
|
||||||
|
emit('ok');
|
||||||
dialogEl.value?.close();
|
dialogEl.value?.close();
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 設定
|
//#region 設定
|
||||||
const useWatermark = computed(defaultStore.makeGetterSetter('useWatermark'));
|
const useWatermark = computed(defaultStore.makeGetterSetter('useWatermark'));
|
||||||
|
const watermarkConfig = ref(defaultStore.state.watermarkConfig);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Canvasの制御
|
//#region Canvasの制御
|
||||||
|
|
109
packages/frontend/src/scripts/watermark.ts
Normal file
109
packages/frontend/src/scripts/watermark.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import { getProxiedImageUrl } from "@/scripts/media-proxy.js";
|
||||||
|
|
||||||
|
export type WatermarkConfig = {
|
||||||
|
fileId: string | null;
|
||||||
|
fileUrl: string | null;
|
||||||
|
width: number | null;
|
||||||
|
height: number | null;
|
||||||
|
enlargement: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
||||||
|
gravity: 'auto' | 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
opacity: number;
|
||||||
|
repeat: true | false | 'x' | 'y';
|
||||||
|
anchor: 'center' | 'top' | 'left' | 'bottom' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||||
|
offsetTop: number | null;
|
||||||
|
offsetLeft: number | null;
|
||||||
|
offsetBottom: number | null;
|
||||||
|
offsetRight: number | null;
|
||||||
|
backgroundColor: string | null;
|
||||||
|
rotate: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ウォーターマークを適用してキャンバスに描画する
|
||||||
|
*
|
||||||
|
* @param img ウォーターマークを適用する画像(stringは画像URL。**プレビュー用途専用**)
|
||||||
|
* @param el ウォーターマークを適用するキャンバス
|
||||||
|
* @param config ウォーターマークの設定
|
||||||
|
*/
|
||||||
|
export function applyWatermark(img: string | Blob, el: HTMLCanvasElement, config: WatermarkConfig) {
|
||||||
|
const canvas = el;
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
const imgEl = new Image();
|
||||||
|
imgEl.onload = () => {
|
||||||
|
canvas.width = imgEl.width;
|
||||||
|
canvas.height = imgEl.height;
|
||||||
|
ctx.drawImage(imgEl, 0, 0);
|
||||||
|
if (config.fileUrl) {
|
||||||
|
const watermark = new Image();
|
||||||
|
watermark.onload = () => {
|
||||||
|
const width = config.width || watermark.width;
|
||||||
|
const height = config.height || watermark.height;
|
||||||
|
const x = (() => {
|
||||||
|
switch (config.anchor) {
|
||||||
|
case 'center':
|
||||||
|
case 'top':
|
||||||
|
case 'bottom':
|
||||||
|
return (canvas.width - width) / 2;
|
||||||
|
case 'left':
|
||||||
|
case 'top-left':
|
||||||
|
case 'bottom-left':
|
||||||
|
return 0;
|
||||||
|
case 'right':
|
||||||
|
case 'top-right':
|
||||||
|
case 'bottom-right':
|
||||||
|
return canvas.width - width;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const y = (() => {
|
||||||
|
switch (config.anchor) {
|
||||||
|
case 'center':
|
||||||
|
case 'left':
|
||||||
|
case 'right':
|
||||||
|
return (canvas.height - height) / 2;
|
||||||
|
case 'top':
|
||||||
|
case 'top-left':
|
||||||
|
case 'top-right':
|
||||||
|
return 0;
|
||||||
|
case 'bottom':
|
||||||
|
case 'bottom-left':
|
||||||
|
case 'bottom-right':
|
||||||
|
return canvas.height - height;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
ctx.globalAlpha = config.opacity;
|
||||||
|
ctx.drawImage(watermark, x, y, width, height);
|
||||||
|
};
|
||||||
|
watermark.src = config.fileUrl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (typeof img === 'string') {
|
||||||
|
imgEl.src = getProxiedImageUrl(img, undefined, true);
|
||||||
|
} else {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
imgEl.src = reader.result as string;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ウォーターマークを適用した画像をBlobとして取得する
|
||||||
|
*
|
||||||
|
* @param img ウォーターマークを適用する画像
|
||||||
|
* @param config ウォーターマークの設定
|
||||||
|
* @returns ウォーターマークを適用した画像のBlob
|
||||||
|
*/
|
||||||
|
export function getWatermarkAppliedImage(img: Blob, config: WatermarkConfig): Promise<Blob> {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
applyWatermark(img, canvas, config);
|
||||||
|
return new Promise<Blob>(resolve => {
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
resolve(blob!);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import { hemisphere } from '@@/js/intl-const.js';
|
||||||
import lightTheme from '@@/themes/l-light.json5';
|
import lightTheme from '@@/themes/l-light.json5';
|
||||||
import darkTheme from '@@/themes/d-green-lime.json5';
|
import darkTheme from '@@/themes/d-green-lime.json5';
|
||||||
import type { SoundType } from '@/scripts/sound.js';
|
import type { SoundType } from '@/scripts/sound.js';
|
||||||
|
import type { WatermarkConfig } from './scripts/watermark.js';
|
||||||
import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
|
import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { Storage } from '@/pizzax.js';
|
import { Storage } from '@/pizzax.js';
|
||||||
|
@ -480,23 +481,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
},
|
},
|
||||||
watermarkConfig: {
|
watermarkConfig: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: null as {
|
default: null as WatermarkConfig | null,
|
||||||
fileId: string | null;
|
|
||||||
fileUrl: string | null;
|
|
||||||
width: number | null;
|
|
||||||
height: number | null;
|
|
||||||
enlargement: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
|
||||||
gravity: 'auto' | 'left' | 'right' | 'top' | 'bottom';
|
|
||||||
opacity: number;
|
|
||||||
repeat: true | false | 'x' | 'y';
|
|
||||||
anchor: 'center' | 'top' | 'left' | 'bottom' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
||||||
offsetTop: number | null;
|
|
||||||
offsetLeft: number | null;
|
|
||||||
offsetBottom: number | null;
|
|
||||||
offsetRight: number | null;
|
|
||||||
backgroundColor: string | null;
|
|
||||||
rotate: number | null;
|
|
||||||
} | null,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
|
|
Loading…
Reference in a new issue