Compare commits
5 Commits
65e0ab810e
...
b8e11516ff
| Author | SHA1 | Date |
|---|---|---|
|
|
b8e11516ff | |
|
|
98f0de6c56 | |
|
|
16a3321287 | |
|
|
88f38e5cd7 | |
|
|
dd4d3a4b61 |
|
|
@ -12071,6 +12071,10 @@ export interface Locale extends ILocale {
|
||||||
* エフェクト
|
* エフェクト
|
||||||
*/
|
*/
|
||||||
"title": string;
|
"title": string;
|
||||||
|
/**
|
||||||
|
* エフェクトを追加
|
||||||
|
*/
|
||||||
|
"addEffect": string;
|
||||||
"_fxs": {
|
"_fxs": {
|
||||||
/**
|
/**
|
||||||
* 色収差
|
* 色収差
|
||||||
|
|
@ -12084,6 +12088,26 @@ export interface Locale extends ILocale {
|
||||||
* ミラー
|
* ミラー
|
||||||
*/
|
*/
|
||||||
"mirror": string;
|
"mirror": string;
|
||||||
|
/**
|
||||||
|
* 色の反転
|
||||||
|
*/
|
||||||
|
"invert": string;
|
||||||
|
/**
|
||||||
|
* 白黒
|
||||||
|
*/
|
||||||
|
"grayscale": string;
|
||||||
|
/**
|
||||||
|
* 色の圧縮
|
||||||
|
*/
|
||||||
|
"colorClamp": string;
|
||||||
|
/**
|
||||||
|
* 色の圧縮(高度)
|
||||||
|
*/
|
||||||
|
"colorClampAdvanced": string;
|
||||||
|
/**
|
||||||
|
* 歪み
|
||||||
|
*/
|
||||||
|
"distort": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3234,8 +3234,14 @@ _watermarkEditor:
|
||||||
|
|
||||||
_imageEffector:
|
_imageEffector:
|
||||||
title: "エフェクト"
|
title: "エフェクト"
|
||||||
|
addEffect: "エフェクトを追加"
|
||||||
|
|
||||||
_fxs:
|
_fxs:
|
||||||
chromaticAberration: "色収差"
|
chromaticAberration: "色収差"
|
||||||
glitch: "グリッチ"
|
glitch: "グリッチ"
|
||||||
mirror: "ミラー"
|
mirror: "ミラー"
|
||||||
|
invert: "色の反転"
|
||||||
|
grayscale: "白黒"
|
||||||
|
colorClamp: "色の圧縮"
|
||||||
|
colorClampAdvanced: "色の圧縮(高度)"
|
||||||
|
distort: "歪み"
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { FXS, ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.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';
|
||||||
|
|
@ -50,6 +50,7 @@ import * as os from '@/os.js';
|
||||||
import { selectFile } from '@/utility/drive.js';
|
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';
|
||||||
|
import { FXS } from '@/utility/image-effector/fxs.js';
|
||||||
|
|
||||||
const layer = defineModel<ImageEffectorLayer>('layer', { required: true });
|
const layer = defineModel<ImageEffectorLayer>('layer', { required: true });
|
||||||
const fx = FXS.find((fx) => fx.id === layer.value.fxId);
|
const fx = FXS.find((fx) => fx.id === layer.value.fxId);
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.previewContainer">
|
<div :class="$style.previewContainer">
|
||||||
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
|
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
|
||||||
<div class="_acrylic" :class="$style.previewControls">
|
<div class="_acrylic" :class="$style.previewControls">
|
||||||
|
<button class="_button" :class="[$style.previewControlsButton, !enabled ? $style.active : null]" @click="enabled = false">Before</button>
|
||||||
|
<button class="_button" :class="[$style.previewControlsButton, enabled ? $style.active : null]" @click="enabled = true">After</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.controls" class="_gaps">
|
<div :class="$style.controls">
|
||||||
|
<div class="_spacer _gaps">
|
||||||
<XLayer
|
<XLayer
|
||||||
v-for="(layer, i) in layers"
|
v-for="(layer, i) in layers"
|
||||||
:key="layer.id"
|
:key="layer.id"
|
||||||
|
|
@ -34,7 +37,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@del="onLayerDelete(layer)"
|
@del="onLayerDelete(layer)"
|
||||||
></XLayer>
|
></XLayer>
|
||||||
|
|
||||||
<MkButton rounded primary @click="addEffect"><i class="ti ti-plus"></i></MkButton>
|
<MkButton rounded primary style="margin: 0 auto;" @click="addEffect"><i class="ti ti-plus"></i> {{ i18n.ts._imageEffector.addEffect }}</MkButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -47,7 +51,7 @@ import { v4 as uuid } from 'uuid';
|
||||||
import type { WatermarkPreset } from '@/utility/watermark.js';
|
import type { WatermarkPreset } from '@/utility/watermark.js';
|
||||||
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { FXS, ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { ImageEffector } from '@/utility/image-effector/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';
|
||||||
|
|
@ -55,6 +59,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import XLayer from '@/components/MkImageEffectorDialog.Layer.vue';
|
import XLayer from '@/components/MkImageEffectorDialog.Layer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
|
import { FXS } from '@/utility/image-effector/fxs.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
image: HTMLImageElement;
|
image: HTMLImageElement;
|
||||||
|
|
@ -112,6 +117,7 @@ onMounted(async () => {
|
||||||
height: props.image.height,
|
height: props.image.height,
|
||||||
layers: layers,
|
layers: layers,
|
||||||
originalImage: props.image,
|
originalImage: props.image,
|
||||||
|
fxs: FXS,
|
||||||
});
|
});
|
||||||
|
|
||||||
await renderer!.bakeTextures();
|
await renderer!.bakeTextures();
|
||||||
|
|
@ -133,6 +139,18 @@ function save() {
|
||||||
dialog.value?.close();
|
dialog.value?.close();
|
||||||
}, 'image/png');
|
}, 'image/png');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enabled = ref(true);
|
||||||
|
watch(enabled, () => {
|
||||||
|
if (renderer != null) {
|
||||||
|
if (enabled.value) {
|
||||||
|
renderer.updateLayers(layers);
|
||||||
|
} else {
|
||||||
|
renderer.updateLayers([]);
|
||||||
|
}
|
||||||
|
renderer.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module>
|
<style module>
|
||||||
|
|
@ -212,7 +230,6 @@ function save() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
padding: 24px;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<slot name="prefix"></slot>
|
<slot name="prefix"></slot>
|
||||||
<div ref="containerEl" class="container">
|
<div ref="containerEl" class="container">
|
||||||
<div class="track">
|
<div class="track">
|
||||||
<div class="highlight" :style="{ width: (steppedRawValue * 100) + '%' }"></div>
|
<div class="highlight right" :style="{ width: ((steppedRawValue - minRatio) * 100) + '%', left: (Math.abs(Math.min(0, min)) / (max + Math.abs(Math.min(0, min)))) * 100 + '%' }">
|
||||||
|
<div class="shine right"></div>
|
||||||
|
</div>
|
||||||
|
<div class="highlight left" :style="{ width: ((minRatio - steppedRawValue) * 100) + '%', left: (steppedRawValue) * 100 + '%' }">
|
||||||
|
<div class="shine left"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="steps && showTicks" class="ticks">
|
<div v-if="steps && showTicks" class="ticks">
|
||||||
<div v-for="i in (steps + 1)" class="tick" :style="{ left: (((i - 1) / steps) * 100) + '%' }"></div>
|
<div v-for="i in (steps + 1)" class="tick" :style="{ left: (((i - 1) / steps) * 100) + '%' }"></div>
|
||||||
|
|
@ -24,7 +29,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@mouseenter.passive="onMouseenter"
|
@mouseenter.passive="onMouseenter"
|
||||||
@mousedown="onMousedown"
|
@mousedown="onMousedown"
|
||||||
@touchstart="onMousedown"
|
@touchstart="onMousedown"
|
||||||
></div>
|
>
|
||||||
|
<div class="thumbInner"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot name="suffix"></slot>
|
<slot name="suffix"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -63,6 +70,9 @@ const emit = defineEmits<{
|
||||||
const containerEl = useTemplateRef('containerEl');
|
const containerEl = useTemplateRef('containerEl');
|
||||||
const thumbEl = useTemplateRef('thumbEl');
|
const thumbEl = useTemplateRef('thumbEl');
|
||||||
|
|
||||||
|
const maxRatio = computed(() => Math.abs(props.max) / (props.max + Math.abs(Math.min(0, props.min))));
|
||||||
|
const minRatio = computed(() => Math.abs(Math.min(0, props.min)) / (props.max + Math.abs(Math.min(0, props.min))));
|
||||||
|
|
||||||
const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
|
const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
|
||||||
const steppedRawValue = computed(() => {
|
const steppedRawValue = computed(() => {
|
||||||
if (props.step) {
|
if (props.step) {
|
||||||
|
|
@ -222,15 +232,17 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$thumbHeight: 20px;
|
$thumbHeight: 32px;
|
||||||
$thumbWidth: 20px;
|
$thumbWidth: 32px;
|
||||||
|
$thumbInnerHeight: 19px;
|
||||||
|
$thumbInnerWidth: 19px;
|
||||||
|
|
||||||
> .body {
|
> .body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 7px 12px;
|
padding: 0px 4px;
|
||||||
background: var(--MI_THEME-panel);
|
background: var(--MI_THEME-panel);
|
||||||
border: solid 1px var(--MI_THEME-panel);
|
border: solid 1px var(--MI_THEME-panel);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
@ -256,10 +268,30 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
|
||||||
> .highlight {
|
> .highlight {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--MI_THEME-accent);
|
background: color(from var(--MI_THEME-buttonGradateA) srgb r g b / 0.5);
|
||||||
opacity: 0.5;
|
overflow: clip;
|
||||||
|
|
||||||
|
> .shine {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 64px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .highlight.right {
|
||||||
|
> .shine.right {
|
||||||
|
right: calc(#{$thumbInnerWidth} / 2);
|
||||||
|
background: linear-gradient(-90deg, var(--MI_THEME-buttonGradateB), color(from var(--MI_THEME-buttonGradateA) srgb r g b / 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .highlight.left {
|
||||||
|
> .shine.left {
|
||||||
|
left: calc(#{$thumbInnerWidth} / 2);
|
||||||
|
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateB), color(from var(--MI_THEME-buttonGradateA) srgb r g b / 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,13 +322,27 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
|
||||||
width: $thumbWidth;
|
width: $thumbWidth;
|
||||||
height: $thumbHeight;
|
height: $thumbHeight;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
background: var(--MI_THEME-accent);
|
|
||||||
border-radius: 999px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
> .thumbInner {
|
||||||
background: hsl(from var(--MI_THEME-accent) h s calc(l + 10));
|
background: hsl(from var(--MI_THEME-accent) h s calc(l + 10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .thumbInner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: $thumbInnerWidth;
|
||||||
|
height: $thumbInnerHeight;
|
||||||
|
background: var(--MI_THEME-accent);
|
||||||
|
border-radius: 999px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import { makeImageEffectorLayers } from '@/utility/watermark.js';
|
import { makeImageEffectorLayers } from '@/utility/watermark.js';
|
||||||
|
import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
|
@ -527,6 +528,7 @@ async function preprocess(item: (typeof items)['value'][number]): Promise<void>
|
||||||
height: img.height,
|
height: img.height,
|
||||||
layers: makeImageEffectorLayers(preset.layers),
|
layers: makeImageEffectorLayers(preset.layers),
|
||||||
originalImage: img,
|
originalImage: img,
|
||||||
|
fxs: [FX_watermarkPlacement],
|
||||||
});
|
});
|
||||||
|
|
||||||
await renderer.bakeTextures();
|
await renderer.bakeTextures();
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.controls" class="_gaps">
|
<div :class="$style.controls">
|
||||||
|
<div class="_spacer _gaps">
|
||||||
<MkSelect v-model="type" :items="[{ label: i18n.ts._watermarkEditor.text, value: 'text' }, { label: i18n.ts._watermarkEditor.image, value: 'image' }]"></MkSelect>
|
<MkSelect v-model="type" :items="[{ label: i18n.ts._watermarkEditor.text, value: 'text' }, { label: i18n.ts._watermarkEditor.image, value: 'image' }]"></MkSelect>
|
||||||
|
|
||||||
<XLayer
|
<XLayer
|
||||||
|
|
@ -39,6 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -57,6 +59,7 @@ import XLayer from '@/components/MkWatermarkEditorDialog.Layer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
|
import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
|
@ -158,6 +161,7 @@ async function initRenderer() {
|
||||||
height: 1000,
|
height: 1000,
|
||||||
layers: makeImageEffectorLayers(preset.layers),
|
layers: makeImageEffectorLayers(preset.layers),
|
||||||
originalImage: sampleImage_3_2,
|
originalImage: sampleImage_3_2,
|
||||||
|
fxs: [FX_watermarkPlacement],
|
||||||
});
|
});
|
||||||
} else if (sampleImageType.value === '2_3') {
|
} else if (sampleImageType.value === '2_3') {
|
||||||
renderer = new ImageEffector({
|
renderer = new ImageEffector({
|
||||||
|
|
@ -166,6 +170,7 @@ async function initRenderer() {
|
||||||
height: 1500,
|
height: 1500,
|
||||||
layers: makeImageEffectorLayers(preset.layers),
|
layers: makeImageEffectorLayers(preset.layers),
|
||||||
originalImage: sampleImage_2_3,
|
originalImage: sampleImage_2_3,
|
||||||
|
fxs: [FX_watermarkPlacement],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +289,6 @@ async function save() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
padding: 24px;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ 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 { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||||
|
import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
preset: WatermarkPreset;
|
preset: WatermarkPreset;
|
||||||
|
|
@ -78,6 +79,7 @@ onMounted(() => {
|
||||||
height: 1000,
|
height: 1000,
|
||||||
layers: makeImageEffectorLayers(props.preset.layers),
|
layers: makeImageEffectorLayers(props.preset.layers),
|
||||||
originalImage: sampleImage,
|
originalImage: sampleImage,
|
||||||
|
fxs: [FX_watermarkPlacement],
|
||||||
});
|
});
|
||||||
|
|
||||||
await renderer.bakeTextures();
|
await renderer.bakeTextures();
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getProxiedImageUrl } from '../media-proxy.js';
|
import { getProxiedImageUrl } from '../media-proxy.js';
|
||||||
import { FX_chromaticAberration } from './fxs/chromaticAberration.js';
|
|
||||||
import { FX_glitch } from './fxs/glitch.js';
|
|
||||||
import { FX_mirror } from './fxs/mirror.js';
|
|
||||||
import { FX_watermarkPlacement } from './fxs/watermarkPlacement.js';
|
|
||||||
|
|
||||||
type ParamTypeToPrimitive = {
|
type ParamTypeToPrimitive = {
|
||||||
'number': number;
|
'number': number;
|
||||||
|
|
@ -48,30 +44,16 @@ export type ImageEffectorFx<ID extends string, P extends ImageEffectorFxParamDef
|
||||||
}) => void;
|
}) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FXS = [
|
export type ImageEffectorLayer = {
|
||||||
FX_watermarkPlacement,
|
|
||||||
FX_chromaticAberration,
|
|
||||||
FX_glitch,
|
|
||||||
FX_mirror,
|
|
||||||
] as const satisfies ImageEffectorFx<string, any>[];
|
|
||||||
|
|
||||||
export type ImageEffectorLayerOf<
|
|
||||||
FXID extends (typeof FXS)[number]['id'],
|
|
||||||
FX extends { params: ImageEffectorFxParamDefs } = Extract<(typeof FXS)[number], { id: FXID }>,
|
|
||||||
> = {
|
|
||||||
id: string;
|
id: string;
|
||||||
fxId: FXID;
|
fxId: string;
|
||||||
params: {
|
params: Record<string, any>;
|
||||||
[key in keyof FX['params']]: ParamTypeToPrimitive[FX['params'][key]['type']];
|
|
||||||
};
|
|
||||||
|
|
||||||
// for watermarkPlacement fx
|
// for watermarkPlacement fx
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
text?: string | null;
|
text?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImageEffectorLayer = ImageEffectorLayerOf<(typeof FXS)[number]['id'], Extract<(typeof FXS)[number], { id: (typeof FXS)[number]['id'] }>>;
|
|
||||||
|
|
||||||
export class ImageEffector {
|
export class ImageEffector {
|
||||||
private canvas: HTMLCanvasElement | null = null;
|
private canvas: HTMLCanvasElement | null = null;
|
||||||
private gl: WebGL2RenderingContext | null = null;
|
private gl: WebGL2RenderingContext | null = null;
|
||||||
|
|
@ -87,6 +69,7 @@ export class ImageEffector {
|
||||||
private shaderCache: Map<string, WebGLProgram> = new Map();
|
private shaderCache: Map<string, WebGLProgram> = new Map();
|
||||||
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
|
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
|
||||||
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
|
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
|
||||||
|
private fxs: ImageEffectorFx<string, any>[];
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
|
|
@ -94,6 +77,7 @@ export class ImageEffector {
|
||||||
height: number;
|
height: number;
|
||||||
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||||
layers: ImageEffectorLayer[];
|
layers: ImageEffectorLayer[];
|
||||||
|
fxs: ImageEffectorFx<string, any>[];
|
||||||
}) {
|
}) {
|
||||||
this.canvas = options.canvas;
|
this.canvas = options.canvas;
|
||||||
this.canvas.width = options.width;
|
this.canvas.width = options.width;
|
||||||
|
|
@ -102,6 +86,7 @@ export class ImageEffector {
|
||||||
this.renderHeight = options.height;
|
this.renderHeight = options.height;
|
||||||
this.originalImage = options.originalImage;
|
this.originalImage = options.originalImage;
|
||||||
this.layers = options.layers;
|
this.layers = options.layers;
|
||||||
|
this.fxs = options.fxs;
|
||||||
this.texturesKey = this.calcTexturesKey();
|
this.texturesKey = this.calcTexturesKey();
|
||||||
|
|
||||||
this.gl = this.canvas.getContext('webgl2', {
|
this.gl = this.canvas.getContext('webgl2', {
|
||||||
|
|
@ -134,7 +119,7 @@ export class ImageEffector {
|
||||||
gl_Position = vec4(position, 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`, `#version 300 es
|
`, `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
uniform sampler2D u_texture;
|
uniform sampler2D u_texture;
|
||||||
|
|
@ -155,7 +140,7 @@ export class ImageEffector {
|
||||||
gl_Position = vec4(position, 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`, `#version 300 es
|
`, `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
uniform sampler2D u_texture;
|
uniform sampler2D u_texture;
|
||||||
|
|
@ -326,7 +311,7 @@ export class ImageEffector {
|
||||||
throw new Error('gl is not initialized');
|
throw new Error('gl is not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
const fx = FXS.find(fx => fx.id === layer.fxId);
|
const fx = this.fxs.find(fx => fx.id === layer.fxId);
|
||||||
if (fx == null) return;
|
if (fx == null) return;
|
||||||
|
|
||||||
const watermark = layer.fxId === 'watermarkPlacement' ? this.bakedTexturesForWatermarkFx.get(layer.id) : undefined;
|
const watermark = layer.fxId === 'watermarkPlacement' ? this.bakedTexturesForWatermarkFx.get(layer.id) : undefined;
|
||||||
|
|
@ -347,6 +332,9 @@ export class ImageEffector {
|
||||||
|
|
||||||
gl.useProgram(shaderProgram);
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
const u_resolution = gl.getUniformLocation(shaderProgram, 'u_resolution');
|
||||||
|
gl.uniform2fv(u_resolution, [this.renderWidth, this.renderHeight]);
|
||||||
|
|
||||||
fx.main({
|
fx.main({
|
||||||
gl: gl,
|
gl: gl,
|
||||||
program: shaderProgram,
|
program: shaderProgram,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FX_chromaticAberration } from './fxs/chromaticAberration.js';
|
||||||
|
import { FX_colorClamp } from './fxs/colorClamp.js';
|
||||||
|
import { FX_colorClampAdvanced } from './fxs/colorClampAdvanced.js';
|
||||||
|
import { FX_distort } from './fxs/distort.js';
|
||||||
|
import { FX_glitch } from './fxs/glitch.js';
|
||||||
|
import { FX_grayscale } from './fxs/grayscale.js';
|
||||||
|
import { FX_invert } from './fxs/invert.js';
|
||||||
|
import { FX_mirror } from './fxs/mirror.js';
|
||||||
|
import { FX_watermarkPlacement } from './fxs/watermarkPlacement.js';
|
||||||
|
import type { ImageEffectorFx } from './ImageEffector.js';
|
||||||
|
|
||||||
|
export const FXS = [
|
||||||
|
FX_watermarkPlacement,
|
||||||
|
FX_chromaticAberration,
|
||||||
|
FX_glitch,
|
||||||
|
FX_mirror,
|
||||||
|
FX_invert,
|
||||||
|
FX_grayscale,
|
||||||
|
FX_colorClamp,
|
||||||
|
FX_colorClampAdvanced,
|
||||||
|
FX_distort,
|
||||||
|
] as const satisfies ImageEffectorFx<string, any>[];
|
||||||
|
|
@ -7,7 +7,7 @@ import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const shader = `#version 300 es
|
const shader = `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
uniform sampler2D u_texture;
|
uniform sampler2D u_texture;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const shader = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform float u_max;
|
||||||
|
uniform float u_min;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 in_color = texture(u_texture, in_uv);
|
||||||
|
float r = min(max(in_color.r, u_min), u_max);
|
||||||
|
float g = min(max(in_color.g, u_min), u_max);
|
||||||
|
float b = min(max(in_color.b, u_min), u_max);
|
||||||
|
out_color = vec4(r, g, b, in_color.a);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FX_colorClamp = defineImageEffectorFx({
|
||||||
|
id: 'colorClamp' as const,
|
||||||
|
name: i18n.ts._imageEffector._fxs.colorClamp,
|
||||||
|
shader,
|
||||||
|
params: {
|
||||||
|
max: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 1.0,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: -1.0,
|
||||||
|
min: -1.0,
|
||||||
|
max: 0.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
main: ({ gl, program, params, preTexture }) => {
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
|
const u_texture = gl.getUniformLocation(program, 'u_texture');
|
||||||
|
gl.uniform1i(u_texture, 0);
|
||||||
|
|
||||||
|
const u_max = gl.getUniformLocation(program, 'u_max');
|
||||||
|
gl.uniform1f(u_max, params.max);
|
||||||
|
|
||||||
|
const u_min = gl.getUniformLocation(program, 'u_min');
|
||||||
|
gl.uniform1f(u_min, 1.0 + params.min);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const shader = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform float u_rMax;
|
||||||
|
uniform float u_rMin;
|
||||||
|
uniform float u_gMax;
|
||||||
|
uniform float u_gMin;
|
||||||
|
uniform float u_bMax;
|
||||||
|
uniform float u_bMin;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 in_color = texture(u_texture, in_uv);
|
||||||
|
float r = min(max(in_color.r, u_rMin), u_rMax);
|
||||||
|
float g = min(max(in_color.g, u_gMin), u_gMax);
|
||||||
|
float b = min(max(in_color.b, u_bMin), u_bMax);
|
||||||
|
out_color = vec4(r, g, b, in_color.a);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FX_colorClampAdvanced = defineImageEffectorFx({
|
||||||
|
id: 'colorClampAdvanced' as const,
|
||||||
|
name: i18n.ts._imageEffector._fxs.colorClampAdvanced,
|
||||||
|
shader,
|
||||||
|
params: {
|
||||||
|
rMax: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 1.0,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
rMin: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: -1.0,
|
||||||
|
min: -1.0,
|
||||||
|
max: 0.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
gMax: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 1.0,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
gMin: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: -1.0,
|
||||||
|
min: -1.0,
|
||||||
|
max: 0.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
bMax: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 1.0,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
bMin: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: -1.0,
|
||||||
|
min: -1.0,
|
||||||
|
max: 0.0,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
main: ({ gl, program, params, preTexture }) => {
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
|
const u_texture = gl.getUniformLocation(program, 'u_texture');
|
||||||
|
gl.uniform1i(u_texture, 0);
|
||||||
|
|
||||||
|
const u_rMax = gl.getUniformLocation(program, 'u_rMax');
|
||||||
|
gl.uniform1f(u_rMax, params.rMax);
|
||||||
|
|
||||||
|
const u_rMin = gl.getUniformLocation(program, 'u_rMin');
|
||||||
|
gl.uniform1f(u_rMin, 1.0 + params.rMin);
|
||||||
|
|
||||||
|
const u_gMax = gl.getUniformLocation(program, 'u_gMax');
|
||||||
|
gl.uniform1f(u_gMax, params.gMax);
|
||||||
|
|
||||||
|
const u_gMin = gl.getUniformLocation(program, 'u_gMin');
|
||||||
|
gl.uniform1f(u_gMin, 1.0 + params.gMin);
|
||||||
|
|
||||||
|
const u_bMax = gl.getUniformLocation(program, 'u_bMax');
|
||||||
|
gl.uniform1f(u_bMax, params.bMax);
|
||||||
|
|
||||||
|
const u_bMin = gl.getUniformLocation(program, 'u_bMin');
|
||||||
|
gl.uniform1f(u_bMin, 1.0 + params.bMin);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const shader = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform float u_phase;
|
||||||
|
uniform float u_frequency;
|
||||||
|
uniform float u_strength;
|
||||||
|
uniform int u_direction; // 0: vertical, 1: horizontal
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float v = u_direction == 0 ?
|
||||||
|
sin(u_phase + in_uv.y * u_frequency) * u_strength :
|
||||||
|
sin(u_phase + in_uv.x * u_frequency) * u_strength;
|
||||||
|
vec4 in_color = u_direction == 0 ?
|
||||||
|
texture(u_texture, vec2(in_uv.x + v, in_uv.y)) :
|
||||||
|
texture(u_texture, vec2(in_uv.x, in_uv.y + v));
|
||||||
|
out_color = in_color;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FX_distort = defineImageEffectorFx({
|
||||||
|
id: 'distort' as const,
|
||||||
|
name: i18n.ts._imageEffector._fxs.distort,
|
||||||
|
shader,
|
||||||
|
params: {
|
||||||
|
direction: {
|
||||||
|
type: 'number:enum' as const,
|
||||||
|
enum: [{ value: 0, label: 'v' }, { value: 1, label: 'h' }],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
phase: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 50.0,
|
||||||
|
min: 0.0,
|
||||||
|
max: 100,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
frequency: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 50,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 0.1,
|
||||||
|
},
|
||||||
|
strength: {
|
||||||
|
type: 'number' as const,
|
||||||
|
default: 0.1,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
main: ({ gl, program, params, preTexture }) => {
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
|
const u_texture = gl.getUniformLocation(program, 'u_texture');
|
||||||
|
gl.uniform1i(u_texture, 0);
|
||||||
|
|
||||||
|
const u_phase = gl.getUniformLocation(program, 'u_phase');
|
||||||
|
gl.uniform1f(u_phase, params.phase / 10);
|
||||||
|
|
||||||
|
const u_frequency = gl.getUniformLocation(program, 'u_frequency');
|
||||||
|
gl.uniform1f(u_frequency, params.frequency);
|
||||||
|
|
||||||
|
const u_strength = gl.getUniformLocation(program, 'u_strength');
|
||||||
|
gl.uniform1f(u_strength, params.strength);
|
||||||
|
|
||||||
|
const u_direction = gl.getUniformLocation(program, 'u_direction');
|
||||||
|
gl.uniform1i(u_direction, params.direction);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -8,7 +8,7 @@ import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const shader = `#version 300 es
|
const shader = `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
uniform sampler2D u_texture;
|
uniform sampler2D u_texture;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const shader = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
float getBrightness(vec4 color) {
|
||||||
|
return (color.r + color.g + color.b) / 3.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 in_color = texture(u_texture, in_uv);
|
||||||
|
float brightness = getBrightness(in_color);
|
||||||
|
out_color = vec4(brightness, brightness, brightness, in_color.a);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FX_grayscale = defineImageEffectorFx({
|
||||||
|
id: 'grayscale' as const,
|
||||||
|
name: i18n.ts._imageEffector._fxs.grayscale,
|
||||||
|
shader,
|
||||||
|
params: {
|
||||||
|
},
|
||||||
|
main: ({ gl, program, params, preTexture }) => {
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
|
const u_texture = gl.getUniformLocation(program, 'u_texture');
|
||||||
|
gl.uniform1i(u_texture, 0);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const shader = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform bool u_r;
|
||||||
|
uniform bool u_g;
|
||||||
|
uniform bool u_b;
|
||||||
|
uniform bool u_a;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 in_color = texture(u_texture, in_uv);
|
||||||
|
out_color.r = u_r ? 1.0 - in_color.r : in_color.r;
|
||||||
|
out_color.g = u_g ? 1.0 - in_color.g : in_color.g;
|
||||||
|
out_color.b = u_b ? 1.0 - in_color.b : in_color.b;
|
||||||
|
out_color.a = u_a ? 1.0 - in_color.a : in_color.a;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FX_invert = defineImageEffectorFx({
|
||||||
|
id: 'invert' as const,
|
||||||
|
name: i18n.ts._imageEffector._fxs.invert,
|
||||||
|
shader,
|
||||||
|
params: {
|
||||||
|
r: {
|
||||||
|
type: 'boolean' as const,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
g: {
|
||||||
|
type: 'boolean' as const,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
type: 'boolean' as const,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
type: 'boolean' as const,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
main: ({ gl, program, params, preTexture }) => {
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
|
const u_texture = gl.getUniformLocation(program, 'u_texture');
|
||||||
|
gl.uniform1i(u_texture, 0);
|
||||||
|
|
||||||
|
const u_r = gl.getUniformLocation(program, 'u_r');
|
||||||
|
gl.uniform1i(u_r, params.r ? 1 : 0);
|
||||||
|
|
||||||
|
const u_g = gl.getUniformLocation(program, 'u_g');
|
||||||
|
gl.uniform1i(u_g, params.g ? 1 : 0);
|
||||||
|
|
||||||
|
const u_b = gl.getUniformLocation(program, 'u_b');
|
||||||
|
gl.uniform1i(u_b, params.b ? 1 : 0);
|
||||||
|
|
||||||
|
const u_a = gl.getUniformLocation(program, 'u_a');
|
||||||
|
gl.uniform1i(u_a, params.a ? 1 : 0);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -7,7 +7,7 @@ import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const shader = `#version 300 es
|
const shader = `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
uniform sampler2D u_texture;
|
uniform sampler2D u_texture;
|
||||||
|
|
@ -17,7 +17,6 @@ uniform int u_v;
|
||||||
out vec4 out_color;
|
out vec4 out_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 pixel = texture(u_texture, in_uv);
|
|
||||||
vec2 uv = in_uv;
|
vec2 uv = in_uv;
|
||||||
if (u_h == -1 && in_uv.x > 0.5) {
|
if (u_h == -1 && in_uv.x > 0.5) {
|
||||||
uv.x = 1.0 - uv.x;
|
uv.x = 1.0 - uv.x;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
import { defineImageEffectorFx } from '../ImageEffector.js';
|
import { defineImageEffectorFx } from '../ImageEffector.js';
|
||||||
|
|
||||||
const shader = `#version 300 es
|
const shader = `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec2 in_uv;
|
in vec2 in_uv;
|
||||||
uniform sampler2D u_texture_src;
|
uniform sampler2D u_texture_src;
|
||||||
|
|
@ -23,7 +23,7 @@ uniform int u_fitMode; // 0: contain, 1: cover
|
||||||
out vec4 out_color;
|
out vec4 out_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 pixel = texture(u_texture_src, in_uv);
|
vec4 in_color = texture(u_texture_src, in_uv);
|
||||||
|
|
||||||
bool contain = u_fitMode == 0;
|
bool contain = u_fitMode == 0;
|
||||||
|
|
||||||
|
|
@ -49,25 +49,26 @@ void main() {
|
||||||
bool isInside = in_uv.x > x_offset - (x_scale / 2.0) && in_uv.x < x_offset + (x_scale / 2.0) &&
|
bool isInside = in_uv.x > x_offset - (x_scale / 2.0) && in_uv.x < x_offset + (x_scale / 2.0) &&
|
||||||
in_uv.y > y_offset - (y_scale / 2.0) && in_uv.y < y_offset + (y_scale / 2.0);
|
in_uv.y > y_offset - (y_scale / 2.0) && in_uv.y < y_offset + (y_scale / 2.0);
|
||||||
if (!isInside) {
|
if (!isInside) {
|
||||||
out_color = pixel;
|
out_color = in_color;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 watermarkPixel = texture(u_texture_watermark, vec2(
|
vec4 watermark_color = texture(u_texture_watermark, vec2(
|
||||||
(in_uv.x - (x_offset - (x_scale / 2.0))) / x_scale,
|
(in_uv.x - (x_offset - (x_scale / 2.0))) / x_scale,
|
||||||
(in_uv.y - (y_offset - (y_scale / 2.0))) / y_scale
|
(in_uv.y - (y_offset - (y_scale / 2.0))) / y_scale
|
||||||
));
|
));
|
||||||
|
|
||||||
out_color.r = mix(pixel.r, watermarkPixel.r, u_opacity * watermarkPixel.a);
|
out_color.r = mix(in_color.r, watermark_color.r, u_opacity * watermark_color.a);
|
||||||
out_color.g = mix(pixel.g, watermarkPixel.g, u_opacity * watermarkPixel.a);
|
out_color.g = mix(in_color.g, watermark_color.g, u_opacity * watermark_color.a);
|
||||||
out_color.b = mix(pixel.b, watermarkPixel.b, u_opacity * watermarkPixel.a);
|
out_color.b = mix(in_color.b, watermark_color.b, u_opacity * watermark_color.a);
|
||||||
out_color.a = pixel.a * (1.0 - u_opacity * watermarkPixel.a) + watermarkPixel.a * u_opacity;
|
out_color.a = in_color.a * (1.0 - u_opacity * watermark_color.a) + watermark_color.a * u_opacity;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FX_watermarkPlacement = defineImageEffectorFx({
|
export const FX_watermarkPlacement = defineImageEffectorFx({
|
||||||
id: 'watermarkPlacement' as const,
|
id: 'watermarkPlacement' as const,
|
||||||
|
name: '(internal)',
|
||||||
shader,
|
shader,
|
||||||
params: {
|
params: {
|
||||||
cover: {
|
cover: {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export class SnowfallEffect {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private FRAGMENT_SOURCE = `#version 300 es
|
private FRAGMENT_SOURCE = `#version 300 es
|
||||||
precision highp float;
|
precision mediump float;
|
||||||
|
|
||||||
in vec4 v_color;
|
in vec4 v_color;
|
||||||
in float v_rotation;
|
in float v_rotation;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue