Skip to content

Commit

Permalink
Add compositing and blending
Browse files Browse the repository at this point in the history
  • Loading branch information
RobolabGs2 committed Jan 28, 2024
1 parent 6d3e0b3 commit e7dd8d2
Show file tree
Hide file tree
Showing 18 changed files with 772 additions and 135 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@
- Клонирование блоков по кнопке в списке блоков.
- Теперь в списках происходит скролл к активному элементу при его смене.
- При преобразовании блока из фиксированного в свободный селектором в списке блоков, его позиция и размеры сохраняются. Добавлена дополнительная опция преобразования со сбросом, работающая как раньше.
- Теперь можно выбирать режимы смешивания и наложения блока (реализованы на основе [веб-стандарта](https://drafts.fxtf.org/compositing/)).
- Добавлены клеточки на фон канваса для видимости прозрачности.

### Изменено

- Кнопка для добавления блока с изображением стала больше и с текстом.
- У настроек блока вкладки теперь расположены сбоку, а не сверху.
- Теперь эффекты применяются не ко всему целому блоку текста, а к каждой его части (тени/обводка/заливка) отдельно. Это может повлиять на работу некоторых эффектов, если они не аддитивны (например, эффект "Пиксели").

### Исправлено

- Не отображались спрайты для управления эффектами после переключения режима редактирования (перемещение/обрезка). Теперь они отображаются всегда.
- При удалении блока/фрейма текущим выбирается ближайший в списке, а не первый.
- Исправлено падение при попытке открыть фрейм без текстовых блоков.
- Исправлено повторное применение прозрачности при использовании эффектов.

## [0.2.6] - 2023-10-29

Expand Down
7 changes: 5 additions & 2 deletions src/lib/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
style: defaultStyle
}
},
effects: []
effects: [],
layer: { blendMode: 'normal', composeMode: 'source_over' }
}
]
}
Expand All @@ -58,7 +59,9 @@
activeFrame.subscribe((frame) => {
if (activeFrameId == frame.id) return;
activeFrameId = frame.id;
activeBlock.set(frame.blocks.find((b) => b.content.type == 'text')!);
activeBlock.set(
frame.blocks.find((b) => b.content.type == 'text') || frame.blocks[frame.blocks.length - 1]
);
});
const editorState = new StateStore<BlockEditorState>({
Expand Down
4 changes: 3 additions & 1 deletion src/lib/Canvas.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script lang="ts">
import checkered from './resources/transparency_background.png';
export let webgl: HTMLCanvasElement;
export let ui: HTMLCanvasElement;
</script>

<article on:dragover on:drop>
<canvas bind:this={webgl} />
<canvas bind:this={webgl} style={`background-image: url(${checkered})`} />
<canvas bind:this={ui} />
</article>

Expand Down
138 changes: 138 additions & 0 deletions src/lib/LayerInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<script lang="ts">
import type { LayerSettings } from './meme';
import Select from './base/Select.svelte';
import type { BlendMode, ComposeMode } from './graphics/graphics';
import Label from './base/Label.svelte';
import Checkbox from './base/Checkbox.svelte';
import { slide } from 'svelte/transition';
export let value: LayerSettings;
const blendModesMap: Record<BlendMode, string> = {
normal: 'Нормальный',
multiply: 'Умножение',
screen: 'Экранное осветление',
overlay: 'Перекрытие',
darken: 'Замена тёмным',
lighten: 'Замена светлым',
color_dodge: 'Осветление основы',
color_burn: 'Затемнение основы',
hard_light: 'Направленный свет',
soft_light: '',
difference: 'Разница',
exclusion: 'Исключение',
hue: '',
saturation: '',
color: '',
luminosity: '',
xor: 'Xor'
};
const blendModes = Object.keys(blendModesMap) as BlendMode[];
const composeModesMap: Record<ComposeMode, string> = {
clear: '',
copy: '',
destination: '',
source_over: '',
destination_over: '',
source_in: '',
destination_in: '',
source_out: '',
destination_out: '',
source_atop: '',
destination_atop: '',
lighter: '',
xor: 'Xor'
};
const composeModes = Object.keys(composeModesMap) as ComposeMode[];
const layerPresets: Array<{ name: string; value: LayerSettings }> = [
{ name: 'Нормальный', value: { blendMode: 'normal', composeMode: 'source_over' } },
{ name: 'Умножение', value: { blendMode: 'multiply', composeMode: 'source_over' } },
{ name: 'Добавление', value: { blendMode: 'normal', composeMode: 'lighter' } },
{ name: 'Экранное осветление', value: { blendMode: 'screen', composeMode: 'source_over' } },
{ name: 'Перекрытие', value: { blendMode: 'overlay', composeMode: 'source_over' } },
{ name: 'Замена тёмным', value: { blendMode: 'darken', composeMode: 'source_over' } },
{ name: 'Замена светлым', value: { blendMode: 'lighten', composeMode: 'source_over' } },
{ name: 'Осветление основы', value: { blendMode: 'color_dodge', composeMode: 'source_over' } },
{ name: 'Затемнение основы', value: { blendMode: 'color_burn', composeMode: 'source_over' } },
{ name: 'Направленный свет', value: { blendMode: 'hard_light', composeMode: 'source_over' } },
{ name: 'Разница', value: { blendMode: 'difference', composeMode: 'source_over' } },
{ name: 'Исключение', value: { blendMode: 'exclusion', composeMode: 'source_over' } },
{ name: 'Xor', value: { blendMode: 'xor', composeMode: 'source_over' } }
];
let expertMode = true;
const selectCss = {
height: '32px',
width: '100%',
main: ''
};
</script>

<main>
<article>
<section>Режим:</section>
<section>
<Select
css={selectCss}
value={layerPresets.find(
({ value: { blendMode, composeMode } }) =>
value.blendMode == blendMode && value.composeMode == composeMode
)}
items={layerPresets}
let:item
on:change={(event) => {
const preset = event.detail.value;
if (!preset) return;
value.blendMode = preset.value.blendMode;
value.composeMode = preset.value.composeMode;
}}
>
{item?.name || 'Иное'}
</Select>
</section>
</article>
<Label>
<span>Экспертный режим</span>
<Checkbox bind:value={expertMode} />
</Label>
{#if expertMode}
<div transition:slide|local>
<article>
<section>Режим смешивания:</section>
<section>
<Select css={selectCss} bind:value={value.blendMode} items={blendModes} let:item>
{blendModesMap[item] || item.replace('_', ' ')}
</Select>
</section>
</article>
<article>
<section>Режим наложения:</section>
<section>
<Select css={selectCss} bind:value={value.composeMode} items={composeModes} let:item>
{composeModesMap[item] || item.replace('_', ' ')}
</Select>
</section>
</article>
</div>
{/if}
</main>

<style lang="scss">
main {
padding-left: 4px;
}
article {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
margin-bottom: 8px;
}
section:first-child {
flex: 3;
}
section:last-child {
flex: 4;
}
</style>
48 changes: 43 additions & 5 deletions src/lib/MemeEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import FramePreview from './FramePreview.svelte';
import ContainerSettings from './ContainerSettings.svelte';
import {
IconAbc,
IconBorderCorners,
IconNewSection,
IconPhoto,
IconPhotoUp,
Expand All @@ -57,6 +59,9 @@
import type { BlockEditorState } from './legacy/rectangle_editor';
import ImageContentPreview from './image/ImageContentPreview.svelte';
import ImageContentSettings from './image/ImageContentSettings.svelte';
import JsonView from './debug/JsonView.svelte';
import SvgIcon from './base/SvgIcon.svelte';
import LayerInput from './LayerInput.svelte';
export let meme: Meme;
export let frame: Frame;
Expand Down Expand Up @@ -279,12 +284,38 @@
{/if}
</PreviewsContainer>
<TabsContainer
tabs={block.content.type === 'text'
? ['Текст', 'Контейнер', 'Эффекты']
: ['Изображение', 'Контейнер', 'Эффекты']}
layout="horizontal"
tabs={[
block.content.type === 'text' ? 'Текст' : 'Изображение',
'Контейнер',
'Эффекты',
'Наложение'
]}
let:tab
>
<span>{tab}</span>
<div class="center" title={tab}>
{#if tab == 'Текст'}
<IconAbc size={24} />
{:else if tab == 'Изображение'}
<IconPhoto size={20} />
{:else if tab == 'Контейнер'}
<IconBorderCorners size={24} />
{:else if tab == 'Эффекты'}
<SvgIcon
size={20}
type="bootstrap"
path="M9.5 2.672a.5.5 0 1 0 1 0V.843a.5.5 0 0 0-1 0zm4.5.035A.5.5 0 0 0 13.293 2L12 3.293a.5.5 0 1 0 .707.707zM7.293 4A.5.5 0 1 0 8 3.293L6.707 2A.5.5 0 0 0 6 2.707zm-.621 2.5a.5.5 0 1 0 0-1H4.843a.5.5 0 1 0 0 1zm8.485 0a.5.5 0 1 0 0-1h-1.829a.5.5 0 0 0 0 1zM13.293 10A.5.5 0 1 0 14 9.293L12.707 8a.5.5 0 1 0-.707.707zM9.5 11.157a.5.5 0 0 0 1 0V9.328a.5.5 0 0 0-1 0zm1.854-5.097a.5.5 0 0 0 0-.706l-.708-.708a.5.5 0 0 0-.707 0L8.646 5.94a.5.5 0 0 0 0 .707l.708.708a.5.5 0 0 0 .707 0l1.293-1.293Zm-3 3a.5.5 0 0 0 0-.706l-.708-.708a.5.5 0 0 0-.707 0L.646 13.94a.5.5 0 0 0 0 .707l.708.708a.5.5 0 0 0 .707 0z"
/>
{:else if tab == 'Наложение'}
<SvgIcon
size={20}
type="bootstrap"
path="M8.235 1.559a.5.5 0 0 0-.47 0l-7.5 4a.5.5 0 0 0 0 .882L3.188 8 .264 9.559a.5.5 0 0 0 0 .882l7.5 4a.5.5 0 0 0 .47 0l7.5-4a.5.5 0 0 0 0-.882L12.813 8l2.922-1.559a.5.5 0 0 0 0-.882zm3.515 7.008L14.438 10 8 13.433 1.562 10 4.25 8.567l3.515 1.874a.5.5 0 0 0 .47 0zM8 9.433 1.562 6 8 2.567 14.438 6z"
/>
{:else}
<IconNewSection />
{/if}
</div>
<div slot="content">
{#if tab === 'Текст' && block.content.type == 'text'}
<TextContentSettings bind:content={block.content.value} on:change on:addPattern />
Expand All @@ -301,6 +332,10 @@
/>
{:else if tab === 'Эффекты'}
<EffectInput shaders={effectsShaders} context={{ frame }} bind:value={block.effects} />
{:else if tab === 'Наложение'}
<LayerInput bind:value={block.layer} />
{:else}
<JsonView bind:value={block} />
{/if}
</div>
</TabsContainer>
Expand Down Expand Up @@ -331,7 +366,7 @@
}
section:nth-of-type(3) {
flex: 5 1;
min-width: 300px;
min-width: 360px;
/* padding: 2px; */
/* overflow: auto; */
/* display: flex; */
Expand Down Expand Up @@ -362,4 +397,7 @@
padding-right: 8px;
text-align: left;
}
.center {
display: flex;
}
</style>
2 changes: 1 addition & 1 deletion src/lib/base/Checkbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
}
span {
position: absolute;
top: 0;
top: -5px;
left: 0;
height: 25px;
width: 25px;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/base/Label.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
<style lang="scss">
label {
position: relative;
padding: 4px;
padding-top: 4px;
padding-bottom: 4px;
margin-bottom: 4px;
display: flex;
justify-content: space-between;
Expand Down
Loading

0 comments on commit e7dd8d2

Please sign in to comment.