wip
This commit is contained in:
parent
33486ebdf2
commit
09eb631fdc
|
@ -96,7 +96,7 @@ import { isWebpSupported } from '@/utility/isWebpSupported.js';
|
||||||
import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
|
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 { Watermarker } from '@/utility/watermarker.js';
|
import { ImageEffector } from '@/utility/ImageEffector.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ const items = ref<{
|
||||||
uploaded: Misskey.entities.DriveFile | null;
|
uploaded: Misskey.entities.DriveFile | null;
|
||||||
uploadFailed: boolean;
|
uploadFailed: boolean;
|
||||||
aborted: boolean;
|
aborted: boolean;
|
||||||
compressionLevel: 0 | 1 | 2 | 3;
|
compressionLevel: number;
|
||||||
compressedSize?: number | null;
|
compressedSize?: number | null;
|
||||||
preprocessedFile?: Blob | null;
|
preprocessedFile?: Blob | null;
|
||||||
file: File;
|
file: File;
|
||||||
|
@ -486,11 +486,11 @@ async function preprocess(item: (typeof items)['value'][number]): Promise<void>
|
||||||
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
||||||
if (needsWatermark && preset != null) {
|
if (needsWatermark && preset != null) {
|
||||||
const canvas = window.document.createElement('canvas');
|
const canvas = window.document.createElement('canvas');
|
||||||
const renderer = new Watermarker({
|
const renderer = new ImageEffector({
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
width: img.width,
|
width: img.width,
|
||||||
height: img.height,
|
height: img.height,
|
||||||
preset: preset,
|
layers: preset.layers,
|
||||||
originalImage: img,
|
originalImage: img,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -87,9 +87,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
|
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { WatermarkerLayer, WatermarkPreset } from '@/utility/watermarker.js';
|
import type { ImageEffectorLayer } from '@/utility/ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { Watermarker } from '@/utility/watermarker.js';
|
import { ImageEffector } from '@/utility/ImageEffector.js';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -102,7 +102,7 @@ import { selectFile } from '@/utility/drive.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
const layer = defineModel<WatermarkerLayer>('layer', { required: true });
|
const layer = defineModel<ImageEffectorLayer>('layer', { required: true });
|
||||||
|
|
||||||
const driveFile = ref();
|
const driveFile = ref();
|
||||||
const driveFileError = ref(false);
|
const driveFileError = ref(false);
|
||||||
|
|
|
@ -45,9 +45,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { WatermarkPreset } from '@/utility/watermarker.js';
|
import type { WatermarkPreset } from '@/preferences/def.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { Watermarker } from '@/utility/watermarker.js';
|
import { ImageEffector } from '@/utility/ImageEffector.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';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -121,7 +121,7 @@ watch(type, () => {
|
||||||
|
|
||||||
watch(preset, async (newValue, oldValue) => {
|
watch(preset, async (newValue, oldValue) => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.updatePreset(preset);
|
renderer.updateLayers(preset.layers);
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
|
@ -148,25 +148,25 @@ watch(sampleImageType, async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let renderer: Watermarker | null = null;
|
let renderer: ImageEffector | 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 Watermarker({
|
renderer = new ImageEffector({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: 1500,
|
width: 1500,
|
||||||
height: 1000,
|
height: 1000,
|
||||||
preset: preset,
|
layers: preset.layers,
|
||||||
originalImage: sampleImage_3_2,
|
originalImage: sampleImage_3_2,
|
||||||
});
|
});
|
||||||
} else if (sampleImageType.value === '2_3') {
|
} else if (sampleImageType.value === '2_3') {
|
||||||
renderer = new Watermarker({
|
renderer = new ImageEffector({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: 1000,
|
width: 1000,
|
||||||
height: 1500,
|
height: 1500,
|
||||||
preset: preset,
|
layers: preset.layers,
|
||||||
originalImage: sampleImage_2_3,
|
originalImage: sampleImage_2_3,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
|
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
|
||||||
import type { WatermarkPreset } from '@/utility/watermarker.js';
|
import type { WatermarkPreset } from '@/preferences/def.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import { Watermarker } from '@/utility/watermarker.js';
|
import { ImageEffector } from '@/utility/ImageEffector.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
preset: WatermarkPreset;
|
preset: WatermarkPreset;
|
||||||
|
@ -64,18 +64,18 @@ const canvasEl = useTemplateRef('canvasEl');
|
||||||
const sampleImage = new Image();
|
const sampleImage = new Image();
|
||||||
sampleImage.src = '/client-assets/sample/3-2.jpg';
|
sampleImage.src = '/client-assets/sample/3-2.jpg';
|
||||||
|
|
||||||
let renderer: Watermarker | null = null;
|
let renderer: ImageEffector | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
sampleImage.onload = async () => {
|
sampleImage.onload = async () => {
|
||||||
watch(canvasEl, async () => {
|
watch(canvasEl, async () => {
|
||||||
if (canvasEl.value == null) return;
|
if (canvasEl.value == null) return;
|
||||||
|
|
||||||
renderer = new Watermarker({
|
renderer = new ImageEffector({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: 1500,
|
width: 1500,
|
||||||
height: 1000,
|
height: 1000,
|
||||||
preset: props.preset,
|
layers: props.preset.layers,
|
||||||
originalImage: sampleImage,
|
originalImage: sampleImage,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ onUnmounted(() => {
|
||||||
|
|
||||||
watch(() => props.preset, async () => {
|
watch(() => props.preset, async () => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.updatePreset(props.preset);
|
renderer.updateLayers(props.preset.layers);
|
||||||
await renderer.bakeTextures();
|
await renderer.bakeTextures();
|
||||||
renderer.render();
|
renderer.render();
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ import { computed, defineAsyncComponent, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import XWatermarkItem from './drive.WatermarkItem.vue';
|
import XWatermarkItem from './drive.WatermarkItem.vue';
|
||||||
import type { WatermarkPreset } from '@/utility/watermarker.js';
|
import type { WatermarkPreset } from '@/preferences/def.js';
|
||||||
import FormLink from '@/components/form/link.vue';
|
import FormLink from '@/components/form/link.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { Plugin } from '@/plugin.js';
|
||||||
import type { DeviceKind } from '@/utility/device-kind.js';
|
import type { DeviceKind } from '@/utility/device-kind.js';
|
||||||
import type { DeckProfile } from '@/deck.js';
|
import type { DeckProfile } from '@/deck.js';
|
||||||
import type { PreferencesDefinition } from './manager.js';
|
import type { PreferencesDefinition } from './manager.js';
|
||||||
import type { WatermarkPreset } from '@/utility/watermarker.js';
|
import type { ImageEffectorLayer } from '@/utility/ImageEffector.js';
|
||||||
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
|
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
|
||||||
|
|
||||||
/** サウンド設定 */
|
/** サウンド設定 */
|
||||||
|
@ -30,6 +30,12 @@ export type SoundStore = {
|
||||||
volume: number;
|
volume: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WatermarkPreset = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
layers: ImageEffectorLayer[];
|
||||||
|
};
|
||||||
|
|
||||||
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
||||||
|
|
||||||
export const PREF_DEF = {
|
export const PREF_DEF = {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const IMAGE_ADD_SHADER = `#version 300 es
|
const IMAGE_PLACEMENT_SHADER = `#version 300 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
|
@ -51,13 +51,7 @@ void main() {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type WatermarkPreset = {
|
type ImageEffectorTextLayer = {
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
layers: WatermarkerLayer[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type WatermarkerTextLayer = {
|
|
||||||
id: string;
|
id: string;
|
||||||
type: 'text';
|
type: 'text';
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -68,7 +62,7 @@ type WatermarkerTextLayer = {
|
||||||
opacity: number;
|
opacity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type WatermarkerImageLayer = {
|
type ImageEffectorImageLayer = {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'image';
|
type: 'image';
|
||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
|
@ -80,9 +74,9 @@ type WatermarkerImageLayer = {
|
||||||
opacity: number;
|
opacity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WatermarkerLayer = WatermarkerTextLayer | WatermarkerImageLayer;
|
export type ImageEffectorLayer = ImageEffectorTextLayer | ImageEffectorImageLayer;
|
||||||
|
|
||||||
export class Watermarker {
|
export class ImageEffector {
|
||||||
private canvas: HTMLCanvasElement | null = null;
|
private canvas: HTMLCanvasElement | null = null;
|
||||||
private gl: WebGL2RenderingContext | null = null;
|
private gl: WebGL2RenderingContext | null = null;
|
||||||
private renderTextureProgram!: WebGLProgram;
|
private renderTextureProgram!: WebGLProgram;
|
||||||
|
@ -90,7 +84,7 @@ export class Watermarker {
|
||||||
private renderWidth!: number;
|
private renderWidth!: number;
|
||||||
private renderHeight!: number;
|
private renderHeight!: number;
|
||||||
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||||
private preset: WatermarkPreset;
|
private layers: ImageEffectorLayer[];
|
||||||
private originalImageTexture: WebGLTexture;
|
private originalImageTexture: WebGLTexture;
|
||||||
private resultTexture: WebGLTexture;
|
private resultTexture: WebGLTexture;
|
||||||
private resultFrameBuffer: WebGLFramebuffer;
|
private resultFrameBuffer: WebGLFramebuffer;
|
||||||
|
@ -102,7 +96,7 @@ export class Watermarker {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||||
preset: WatermarkPreset;
|
layers: ImageEffectorLayer[];
|
||||||
}) {
|
}) {
|
||||||
this.canvas = options.canvas;
|
this.canvas = options.canvas;
|
||||||
this.canvas.width = options.width;
|
this.canvas.width = options.width;
|
||||||
|
@ -110,7 +104,7 @@ export class Watermarker {
|
||||||
this.renderWidth = options.width;
|
this.renderWidth = options.width;
|
||||||
this.renderHeight = options.height;
|
this.renderHeight = options.height;
|
||||||
this.originalImage = options.originalImage;
|
this.originalImage = options.originalImage;
|
||||||
this.preset = options.preset;
|
this.layers = options.layers;
|
||||||
this.texturesKey = this.calcTexturesKey();
|
this.texturesKey = this.calcTexturesKey();
|
||||||
|
|
||||||
this.gl = this.canvas.getContext('webgl2', {
|
this.gl = this.canvas.getContext('webgl2', {
|
||||||
|
@ -180,7 +174,7 @@ export class Watermarker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private calcTexturesKey() {
|
private calcTexturesKey() {
|
||||||
return this.preset.layers.map(layer => {
|
return this.layers.map(layer => {
|
||||||
if (layer.type === 'image') {
|
if (layer.type === 'image') {
|
||||||
return layer.imageId;
|
return layer.imageId;
|
||||||
} else if (layer.type === 'text') {
|
} else if (layer.type === 'text') {
|
||||||
|
@ -224,10 +218,11 @@ export class Watermarker {
|
||||||
|
|
||||||
this.disposeBakedTextures();
|
this.disposeBakedTextures();
|
||||||
|
|
||||||
for (const layer of this.preset.layers) {
|
for (const layer of this.layers) {
|
||||||
if (layer.type === 'image') {
|
if (layer.type === 'image') {
|
||||||
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'use-credentials';
|
||||||
img.onload = () => resolve(img);
|
img.onload = () => resolve(img);
|
||||||
img.onerror = reject;
|
img.onerror = reject;
|
||||||
img.src = layer.imageUrl;
|
img.src = layer.imageUrl;
|
||||||
|
@ -332,7 +327,7 @@ export class Watermarker {
|
||||||
return shaderProgram;
|
return shaderProgram;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTextOrImageLayer(layer: WatermarkerTextLayer | WatermarkerImageLayer) {
|
private renderTextOrImageLayer(layer: ImageEffectorTextLayer | ImageEffectorImageLayer) {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
if (gl == null) {
|
if (gl == null) {
|
||||||
throw new Error('gl is not initialized');
|
throw new Error('gl is not initialized');
|
||||||
|
@ -351,7 +346,7 @@ export class Watermarker {
|
||||||
in_uv = (position + 1.0) / 2.0;
|
in_uv = (position + 1.0) / 2.0;
|
||||||
gl_Position = vec4(position, 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`, IMAGE_ADD_SHADER);
|
`, IMAGE_PLACEMENT_SHADER);
|
||||||
|
|
||||||
gl.useProgram(shaderProgram);
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
@ -392,7 +387,7 @@ export class Watermarker {
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderLayer(layer: WatermarkerLayer) {
|
private renderLayer(layer: ImageEffectorLayer) {
|
||||||
if (layer.type === 'image') {
|
if (layer.type === 'image') {
|
||||||
this.renderTextOrImageLayer(layer);
|
this.renderTextOrImageLayer(layer);
|
||||||
} else if (layer.type === 'text') {
|
} else if (layer.type === 'text') {
|
||||||
|
@ -435,7 +430,7 @@ export class Watermarker {
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
|
|
||||||
for (const layer of this.preset.layers) {
|
for (const layer of this.layers) {
|
||||||
this.renderLayer(layer);
|
this.renderLayer(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,8 +445,8 @@ export class Watermarker {
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updatePreset(preset: WatermarkPreset) {
|
public async updateLayers(layers: ImageEffectorLayer[]) {
|
||||||
this.preset = preset;
|
this.layers = layers;
|
||||||
|
|
||||||
const newTexturesKey = this.calcTexturesKey();
|
const newTexturesKey = this.calcTexturesKey();
|
||||||
if (newTexturesKey !== this.texturesKey) {
|
if (newTexturesKey !== this.texturesKey) {
|
Loading…
Reference in New Issue