@pixerate/editor-svelte 0.84.0 → 0.85.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/paint-editor/BrushPanel.svelte +67 -0
- package/dist/paint-editor/BrushPanel.svelte.d.ts.map +1 -1
- package/dist/paint-editor/PaintCanvas.svelte +30 -2
- package/dist/paint-editor/PaintCanvas.svelte.d.ts.map +1 -1
- package/dist/paint-editor/PaintEditor.svelte +8 -5
- package/dist/paint-editor/PaintEditor.svelte.d.ts.map +1 -1
- package/dist/paint-editor/PaintToolbar.svelte +32 -1
- package/dist/paint-editor/PaintToolbar.svelte.d.ts.map +1 -1
- package/dist/paint-editor/PaperPanel.svelte +12 -1
- package/dist/paint-editor/PaperPanel.svelte.d.ts.map +1 -1
- package/dist/paint-editor/StickerPanel.svelte +53 -0
- package/dist/paint-editor/StickerPanel.svelte.d.ts +9 -0
- package/dist/paint-editor/StickerPanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/WetPanel.svelte +66 -0
- package/dist/paint-editor/WetPanel.svelte.d.ts +9 -0
- package/dist/paint-editor/WetPanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/index.d.ts +6 -0
- package/dist/paint-editor/index.d.ts.map +1 -1
- package/dist/paint-editor/index.js +2 -0
- package/dist/vector/VectorCanvas.svelte +8 -0
- package/dist/vector/VectorCanvas.svelte.d.ts.map +1 -1
- package/dist/vector/VectorShadingPanel.svelte +66 -0
- package/dist/vector/VectorShadingPanel.svelte.d.ts +14 -0
- package/dist/vector/VectorShadingPanel.svelte.d.ts.map +1 -0
- package/dist/vector/VectorStylePanel.svelte +4 -0
- package/dist/vector/VectorStylePanel.svelte.d.ts.map +1 -1
- package/dist/vector/VectorTracePanel.svelte +76 -0
- package/dist/vector/VectorTracePanel.svelte.d.ts +13 -0
- package/dist/vector/VectorTracePanel.svelte.d.ts.map +1 -0
- package/dist/vector/index.d.ts +2 -0
- package/dist/vector/index.d.ts.map +1 -1
- package/dist/vector/index.js +2 -0
- package/package.json +2 -2
|
@@ -10,8 +10,26 @@
|
|
|
10
10
|
patchBrushValue,
|
|
11
11
|
patchPath,
|
|
12
12
|
readPath,
|
|
13
|
+
WET_TOOLS,
|
|
14
|
+
WET_TOOL_LABELS,
|
|
15
|
+
WET_MEDIA,
|
|
16
|
+
WET_MEDIUM_LABELS,
|
|
17
|
+
IMPASTO_TOOLS,
|
|
18
|
+
IMPASTO_TOOL_LABELS,
|
|
19
|
+
PARTICLE_KINDS,
|
|
20
|
+
PARTICLE_KIND_LABELS,
|
|
21
|
+
PARTICLE_RENDERS,
|
|
22
|
+
PARTICLE_RENDER_LABELS,
|
|
23
|
+
STAMP_KINDS,
|
|
24
|
+
STAMP_LABELS,
|
|
25
|
+
toggleStamp,
|
|
26
|
+
type ParticleKind,
|
|
27
|
+
type ParticleRender,
|
|
28
|
+
type ImpastoTool,
|
|
29
|
+
type WetMediumKind,
|
|
13
30
|
type BlendMode,
|
|
14
31
|
type BrushRotation,
|
|
32
|
+
type WetTool,
|
|
15
33
|
type MultiColorMode,
|
|
16
34
|
type PaintEditorController,
|
|
17
35
|
} from '@pixerate/editor/paint-editor';
|
|
@@ -104,6 +122,55 @@
|
|
|
104
122
|
{#if engine}
|
|
105
123
|
<div class="flex flex-col gap-1.5" data-testid="brush-engine">
|
|
106
124
|
<span class="font-medium text-slate-300">{engine.title}</span>
|
|
125
|
+
{#if engine.wetTool}
|
|
126
|
+
<label class="flex items-center justify-between gap-2">
|
|
127
|
+
<span class="text-slate-400">Wet tool</span>
|
|
128
|
+
<select aria-label="Wet tool" value={brush.wet.tool} onchange={(e) => controller.setBrush({ wet: { tool: e.currentTarget.value as WetTool } })} class="rounded bg-slate-800 px-1 py-0.5">
|
|
129
|
+
{#each WET_TOOLS as t (t)}<option value={t}>{WET_TOOL_LABELS[t]}</option>{/each}
|
|
130
|
+
</select>
|
|
131
|
+
</label>
|
|
132
|
+
{/if}
|
|
133
|
+
{#if engine.wetTool}
|
|
134
|
+
<label class="flex items-center justify-between gap-2">
|
|
135
|
+
<span class="text-slate-400">Medium</span>
|
|
136
|
+
<select aria-label="Wet medium" value={brush.wet.medium} onchange={(e) => controller.setBrush({ wet: { medium: e.currentTarget.value as WetMediumKind } })} class="rounded bg-slate-800 px-1 py-0.5">
|
|
137
|
+
{#each WET_MEDIA as m (m)}<option value={m}>{WET_MEDIUM_LABELS[m]}</option>{/each}
|
|
138
|
+
</select>
|
|
139
|
+
</label>
|
|
140
|
+
{/if}
|
|
141
|
+
{#if engine.particleMenus}
|
|
142
|
+
<label class="flex items-center justify-between gap-2">
|
|
143
|
+
<span class="text-slate-400">Particles</span>
|
|
144
|
+
<select aria-label="Particle kind" value={brush.particle.kind} onchange={(e) => controller.setBrush({ particle: { kind: e.currentTarget.value as ParticleKind } })} class="rounded bg-slate-800 px-1 py-0.5">
|
|
145
|
+
{#each PARTICLE_KINDS as k (k)}<option value={k}>{PARTICLE_KIND_LABELS[k]}</option>{/each}
|
|
146
|
+
</select>
|
|
147
|
+
</label>
|
|
148
|
+
<label class="flex items-center justify-between gap-2">
|
|
149
|
+
<span class="text-slate-400">Drawn as</span>
|
|
150
|
+
<select aria-label="Particle render" value={brush.particle.render} onchange={(e) => controller.setBrush({ particle: { render: e.currentTarget.value as ParticleRender } })} class="rounded bg-slate-800 px-1 py-0.5">
|
|
151
|
+
{#each PARTICLE_RENDERS as k (k)}<option value={k}>{PARTICLE_RENDER_LABELS[k]}</option>{/each}
|
|
152
|
+
</select>
|
|
153
|
+
</label>
|
|
154
|
+
{/if}
|
|
155
|
+
{#if engine.stickerStamps}
|
|
156
|
+
<div class="flex flex-wrap gap-1" role="group" aria-label="Sticker stamps">
|
|
157
|
+
{#each STAMP_KINDS as k (k)}
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
aria-pressed={brush.sticker.stamps.includes(k)}
|
|
161
|
+
class={`px-1.5 py-0.5 rounded text-[10px] ${brush.sticker.stamps.includes(k) ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-300'}`}
|
|
162
|
+
onclick={() => controller.setBrush({ sticker: { stamps: toggleStamp(brush.sticker.stamps, k) } })}>{STAMP_LABELS[k]}</button>
|
|
163
|
+
{/each}
|
|
164
|
+
</div>
|
|
165
|
+
{/if}
|
|
166
|
+
{#if engine.impastoTool}
|
|
167
|
+
<label class="flex items-center justify-between gap-2">
|
|
168
|
+
<span class="text-slate-400">Tool</span>
|
|
169
|
+
<select aria-label="Impasto tool" value={brush.impasto.tool} onchange={(e) => controller.setBrush({ impasto: { tool: e.currentTarget.value as ImpastoTool } })} class="rounded bg-slate-800 px-1 py-0.5">
|
|
170
|
+
{#each IMPASTO_TOOLS as t (t)}<option value={t}>{IMPASTO_TOOL_LABELS[t]}</option>{/each}
|
|
171
|
+
</select>
|
|
172
|
+
</label>
|
|
173
|
+
{/if}
|
|
107
174
|
{#each engine.sliders as c (c.path)}
|
|
108
175
|
<PaintSlider control={c} value={readPath(brush, c.path)} onchange={(v) => controller.setBrush(patchPath(brush as never, c.path, v) as never)} />
|
|
109
176
|
{/each}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrushPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/BrushPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"BrushPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/BrushPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAgCH,KAAK,qBAAqB,EAC3B,MAAM,+BAA+B,CAAC;AAOvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoNH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PaintCanvasPresenter,
|
|
4
4
|
PaintGesture,
|
|
5
|
+
drawWaterOverlay,
|
|
5
6
|
fitZoom,
|
|
7
|
+
startWetClock,
|
|
6
8
|
guideOverlay,
|
|
7
9
|
handlePaintShortcut,
|
|
8
10
|
viewCssTransform,
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
const snap = $derived(editor.current);
|
|
36
38
|
let viewport = $state<HTMLDivElement | null>(null);
|
|
37
39
|
let canvas = $state<HTMLCanvasElement | null>(null);
|
|
40
|
+
let water = $state<HTMLCanvasElement | null>(null);
|
|
38
41
|
let size = $state({ width: 800, height: 600 });
|
|
39
42
|
let predicted = $state<Array<{ x: number; y: number }>>([]);
|
|
40
43
|
const presenter = $derived(new PaintCanvasPresenter(controller));
|
|
@@ -83,6 +86,21 @@
|
|
|
83
86
|
});
|
|
84
87
|
});
|
|
85
88
|
|
|
89
|
+
// Wet paint flows with the display.
|
|
90
|
+
$effect(() => {
|
|
91
|
+
const c = controller;
|
|
92
|
+
return untrack(() => startWetClock(c));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// The water overlay.
|
|
96
|
+
$effect(() => {
|
|
97
|
+
void snap;
|
|
98
|
+
const ctx = water?.getContext('2d');
|
|
99
|
+
untrack(() => {
|
|
100
|
+
if (ctx && typeof ctx.createImageData === 'function') drawWaterOverlay(controller, ctx as never);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
86
104
|
// Wheel needs a non-passive listener to prevent page scroll (see GOTCHAS).
|
|
87
105
|
$effect(() => {
|
|
88
106
|
const el = viewport;
|
|
@@ -141,6 +159,16 @@
|
|
|
141
159
|
class="absolute left-0 top-0 shadow-2xl"
|
|
142
160
|
style={`transform: ${transform}; transform-origin: 0 0; image-rendering: ${snap.view.zoom > 2 ? 'pixelated' : 'auto'}`}
|
|
143
161
|
></canvas>
|
|
162
|
+
{#if snap.wet.overlay}
|
|
163
|
+
<canvas
|
|
164
|
+
bind:this={water}
|
|
165
|
+
width={doc.width}
|
|
166
|
+
height={doc.height}
|
|
167
|
+
data-testid="paint-water-overlay"
|
|
168
|
+
class="absolute left-0 top-0 pointer-events-none opacity-60"
|
|
169
|
+
style={`transform: ${transform}; transform-origin: 0 0`}
|
|
170
|
+
></canvas>
|
|
171
|
+
{/if}
|
|
144
172
|
<svg class="absolute left-0 top-0 pointer-events-none overflow-visible" width={doc.width} height={doc.height} style={`transform: ${transform}; transform-origin: 0 0`} aria-hidden="true">
|
|
145
173
|
{#each lines as l, i (i)}
|
|
146
174
|
<line
|
|
@@ -148,8 +176,8 @@
|
|
|
148
176
|
y1={l.from.y}
|
|
149
177
|
x2={l.to.x}
|
|
150
178
|
y2={l.to.y}
|
|
151
|
-
stroke={l.kind === 'symmetry' ? '#22d3ee' : '#f472b6'}
|
|
152
|
-
stroke-opacity={l.kind === 'symmetry' ? 0.8 : 0.35}
|
|
179
|
+
stroke={l.kind === 'symmetry' ? '#22d3ee' : l.kind === 'stencil' ? '#fbbf24' : '#f472b6'}
|
|
180
|
+
stroke-opacity={l.kind === 'symmetry' || l.kind === 'stencil' ? 0.8 : 0.35}
|
|
153
181
|
stroke-width={1 / snap.view.zoom}
|
|
154
182
|
stroke-dasharray={l.kind === 'symmetry' ? `${6 / snap.view.zoom} ${4 / snap.view.zoom}` : undefined}
|
|
155
183
|
/>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaintCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintCanvas.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"PaintCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintCanvas.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EASH,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,+BAA+B,CAAC;AAOvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,uCAAuC;IACvC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA2IH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import PaintLayersPanel from './PaintLayersPanel.svelte';
|
|
7
7
|
import PaintToolbar from './PaintToolbar.svelte';
|
|
8
8
|
import PaperPanel from './PaperPanel.svelte';
|
|
9
|
+
import WetPanel from './WetPanel.svelte';
|
|
10
|
+
import StickerPanel from './StickerPanel.svelte';
|
|
9
11
|
import { chip } from './paintEditorRune.svelte.js';
|
|
10
12
|
|
|
11
13
|
interface Props {
|
|
@@ -20,11 +22,11 @@
|
|
|
20
22
|
}
|
|
21
23
|
let { controller, fileName, height = 720, presence, class: className = '' }: Props = $props();
|
|
22
24
|
let tool = $state<PaintTool>('brush');
|
|
23
|
-
let tab = $state<'color' | 'paper' | 'layers'>('color');
|
|
25
|
+
let tab = $state<'color' | 'wet' | 'paper' | 'layers'>('color');
|
|
24
26
|
let error = $state<string | null>(null);
|
|
25
27
|
</script>
|
|
26
28
|
|
|
27
|
-
<!-- Toolbar, brush library, the canvas, and colour / paper / layers. -->
|
|
29
|
+
<!-- Toolbar, brush library, the canvas, and colour / wet media / paper / layers. -->
|
|
28
30
|
<div class={`flex flex-col gap-2 rounded-xl border border-slate-800 bg-slate-900 p-2 ${className}`} style={`height: ${typeof height === 'number' ? `${height}px` : height}`} data-testid="paint-editor">
|
|
29
31
|
<PaintToolbar {controller} {tool} ontoolchange={(t) => (tool = t)} {fileName} />
|
|
30
32
|
<div class="flex min-h-0 flex-1 gap-2">
|
|
@@ -35,13 +37,14 @@
|
|
|
35
37
|
</div>
|
|
36
38
|
<div class="flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1">
|
|
37
39
|
<div class="flex gap-1" role="tablist" aria-label="Paint panels">
|
|
38
|
-
{#each ['color', 'paper', 'layers'] as const as t (t)}
|
|
39
|
-
<button type="button" role="tab" aria-selected={tab === t} class={chip(tab === t)} onclick={() => (tab = t)}>{t === 'color' ? 'Colour' : t === 'paper' ? 'Paper' : 'Layers'}</button>
|
|
40
|
+
{#each ['color', 'wet', 'paper', 'layers'] as const as t (t)}
|
|
41
|
+
<button type="button" role="tab" aria-selected={tab === t} class={chip(tab === t)} onclick={() => (tab = t)}>{t === 'color' ? 'Colour' : t === 'wet' ? 'Wet' : t === 'paper' ? 'Paper' : 'Layers'}</button>
|
|
40
42
|
{/each}
|
|
41
43
|
</div>
|
|
42
44
|
{#if tab === 'color'}<ColorPanel {controller} />{/if}
|
|
45
|
+
{#if tab === 'wet'}<WetPanel {controller} />{/if}
|
|
43
46
|
{#if tab === 'paper'}<PaperPanel {controller} />{/if}
|
|
44
|
-
{#if tab === 'layers'}<PaintLayersPanel {controller} />{/if}
|
|
47
|
+
{#if tab === 'layers'}<PaintLayersPanel {controller} /><StickerPanel {controller} />{/if}
|
|
45
48
|
</div>
|
|
46
49
|
</div>
|
|
47
50
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaintEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,aAAa,EAAa,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"PaintEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintEditor.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,aAAa,EAAa,MAAM,+BAA+B,CAAC;AAYnG,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4CH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { PAINT_TOOLS, SYMMETRY_MODES, decodeImageBlob, downloadBytes, type PaintEditorController, type PaintTool, type SymmetryMode } from '@pixerate/editor/paint-editor';
|
|
2
|
+
import { PAINT_TOOLS, SYMMETRY_MODES, decodeImageBlob, downloadBytes, quickStencil, timeLapseZip, type PaintEditorController, type PaintTool, type SymmetryMode } from '@pixerate/editor/paint-editor';
|
|
3
3
|
import { attempt, button, chip, paintEditorState } from './paintEditorRune.svelte.js';
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -18,10 +18,32 @@
|
|
|
18
18
|
const tracing = $derived(snap.guides.tracing);
|
|
19
19
|
const canvas = $derived(snap.document.canvas);
|
|
20
20
|
let error = $state<string | null>(null);
|
|
21
|
+
let busy = $state<string | null>(null);
|
|
22
|
+
let note = $state<string | null>(null);
|
|
21
23
|
let openInput = $state<HTMLInputElement | null>(null);
|
|
22
24
|
let tracingInput = $state<HTMLInputElement | null>(null);
|
|
23
25
|
let refInput = $state<HTMLInputElement | null>(null);
|
|
24
26
|
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
27
|
+
const timeLapse = () =>
|
|
28
|
+
run(async () => {
|
|
29
|
+
busy = 'Repainting…';
|
|
30
|
+
try {
|
|
31
|
+
const tl = await controller.timeLapse({ onProgress: (p) => (busy = `Repainting… ${Math.round(p * 100)}%`) });
|
|
32
|
+
downloadBytes(await timeLapseZip(tl), `${fileName}-timelapse.zip`, 'application/zip');
|
|
33
|
+
note = `Time-lapse: ${tl.frames.length} frames, ${(tl.durationMs / 1000).toFixed(1)} s (run render.sh in the zip for the MP4).`;
|
|
34
|
+
} finally {
|
|
35
|
+
busy = null;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const highRes = () =>
|
|
39
|
+
run(async () => {
|
|
40
|
+
busy = 'Repainting at 2×…';
|
|
41
|
+
try {
|
|
42
|
+
downloadBytes(await controller.exportHighResPng({ scale: 2 }), `${fileName}@2x.png`, 'image/png');
|
|
43
|
+
} finally {
|
|
44
|
+
busy = null;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
25
47
|
const exportImage = (format: 'image/png' | 'image/jpeg') =>
|
|
26
48
|
run(async () => downloadBytes(await controller.exportImage({ format }), `${fileName}.${format === 'image/png' ? 'png' : 'jpg'}`, format));
|
|
27
49
|
const vanishing = (n: number) => {
|
|
@@ -88,6 +110,12 @@
|
|
|
88
110
|
<button type="button" class={button} onclick={() => controller.clearTracingImage()}>Remove tracing</button>
|
|
89
111
|
{/if}
|
|
90
112
|
<button type="button" class={button} onclick={() => refInput?.click()}>Reference…</button>
|
|
113
|
+
<span class="flex items-center gap-1" role="group" aria-label="Stencils">
|
|
114
|
+
<button type="button" class={button} title="A ruler: strokes that start near it follow it" onclick={() => run(() => controller.addStencil(quickStencil('ruler', canvas)))}>Ruler</button>
|
|
115
|
+
<button type="button" class={button} title="A circle guide: strokes that start near it follow it" onclick={() => run(() => controller.addStencil(quickStencil('circle', canvas)))}>Circle</button>
|
|
116
|
+
<button type="button" class={button} title="An ellipse mask: paint lands only inside it" onclick={() => run(() => controller.addStencil(quickStencil('ellipse-mask', canvas)))}>Mask</button>
|
|
117
|
+
{#if snap.guides.stencils.length > 0}<button type="button" class={button} onclick={() => run(() => controller.removeStencils())}>Clear stencils</button>{/if}
|
|
118
|
+
</span>
|
|
91
119
|
<button type="button" class={button} title="Which hand holds the pen (palm rejection)" onclick={() => controller.setHand(snap.hand === 'right' ? 'left' : 'right')}>{snap.hand === 'right' ? 'Right-handed' : 'Left-handed'}</button>
|
|
92
120
|
<span class="mx-1 h-4 w-px bg-slate-700"></span>
|
|
93
121
|
<button type="button" class={button} onclick={() => run(() => downloadBytes(JSON.stringify(controller.toJSON()), `${fileName}.paint.json`, 'application/json'))}>Save</button>
|
|
@@ -95,6 +123,9 @@
|
|
|
95
123
|
<button type="button" class={button} onclick={() => exportImage('image/png')}>PNG</button>
|
|
96
124
|
<button type="button" class={button} onclick={() => exportImage('image/jpeg')}>JPEG</button>
|
|
97
125
|
<button type="button" class={button} onclick={() => run(() => downloadBytes(controller.exportPsd().bytes, `${fileName}.psd`, 'image/vnd.adobe.photoshop'))}>PSD</button>
|
|
126
|
+
<button type="button" class={button} disabled={busy !== null} title="Repaint the painting from its strokes as a time-lapse: frames, a video project and the ffmpeg command, zipped" onclick={timeLapse}>Time-lapse</button>
|
|
127
|
+
<button type="button" class={button} disabled={busy !== null} title="Repaint the painting from its strokes at twice the size (more detail, not upscaled)" onclick={highRes}>PNG 2×</button>
|
|
128
|
+
{#if busy ?? note}<span class="text-[11px] text-slate-400" data-testid="paint-replay-status">{busy ?? note}</span>{/if}
|
|
98
129
|
<input
|
|
99
130
|
bind:this={openInput}
|
|
100
131
|
type="file"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaintToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"PaintToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2F,KAAK,qBAAqB,EAAE,KAAK,SAAS,EAAqB,MAAM,+BAA+B,CAAC;AAIrM,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgIH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { PAPER_FAMILIES, PAPER_LIBRARY, PAPER_SLIDERS, patchPath, readPath, renderPaper, type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
2
|
+
import { LIGHT_SLIDERS, PAPER_FAMILIES, PAPER_LIBRARY, PAPER_SLIDERS, patchPath, readPath, renderPaper, type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
3
3
|
import PaintSlider from './PaintSlider.svelte';
|
|
4
4
|
import PixelCanvas from './PixelCanvas.svelte';
|
|
5
5
|
import { attempt, button, paintEditorState, panel } from './paintEditorRune.svelte.js';
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
let error = $state<string | null>(null);
|
|
15
15
|
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
16
16
|
const SWATCH = { x: 0, y: 0, width: 64, height: 40 };
|
|
17
|
+
const lighting = $derived.by(() => {
|
|
18
|
+
void editor.current;
|
|
19
|
+
return controller.getLighting();
|
|
20
|
+
});
|
|
17
21
|
const previews = new Map(PAPER_LIBRARY.map((p) => [p.preset, { ...SWATCH, data: renderPaper({ ...p, shading: Math.max(0.8, p.shading) }, SWATCH) }]));
|
|
18
22
|
</script>
|
|
19
23
|
|
|
@@ -50,5 +54,12 @@
|
|
|
50
54
|
<input type="checkbox" checked={paper.tiling === 'mirror'} onchange={(e) => run(() => controller.setPaper({ tiling: e.currentTarget.checked ? 'mirror' : 'repeat' }))} /> Mirror tiling
|
|
51
55
|
</label>
|
|
52
56
|
<button type="button" class={`self-start ${button}`} onclick={() => run(() => controller.setPaper({ seed: paper.seed + 1 }))}>New sheet</button>
|
|
57
|
+
<div class="flex flex-col gap-1.5" data-testid="light-panel">
|
|
58
|
+
<span class="font-medium text-slate-300">Light (thick paint, metallic paper)</span>
|
|
59
|
+
{#each LIGHT_SLIDERS as c (c.path)}
|
|
60
|
+
<PaintSlider control={c} value={readPath(lighting, c.path)} onchange={(v) => run(() => controller.setLighting({ [c.path]: v }))} />
|
|
61
|
+
{/each}
|
|
62
|
+
<button type="button" class={`self-start ${button}`} onclick={() => run(() => controller.flattenRelief())}>Flatten relief</button>
|
|
63
|
+
</div>
|
|
53
64
|
{#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
|
|
54
65
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaperPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaperPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"PaperPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaperPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiG,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAMxK,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0DH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { STAMP_KINDS, STAMP_LABELS, STICKER_SLIDERS, readPath, type PaintEditorController, type StampKind } from '@pixerate/editor/paint-editor';
|
|
3
|
+
import PaintSlider from './PaintSlider.svelte';
|
|
4
|
+
import { attempt, button, paintEditorState, panel } from './paintEditorRune.svelte.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
controller: PaintEditorController;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
let { controller, class: className = '' }: Props = $props();
|
|
11
|
+
const editor = paintEditorState(() => controller);
|
|
12
|
+
const layerId = $derived(editor.current.document.activeLayerId);
|
|
13
|
+
const stickers = $derived(layerId ? (editor.current.document.stickers?.[layerId] ?? []) : []);
|
|
14
|
+
let picked = $state<string | null>(null);
|
|
15
|
+
const sel = $derived(stickers.find((s) => s.id === picked) ?? null);
|
|
16
|
+
let error = $state<string | null>(null);
|
|
17
|
+
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- The active layer's stickers: pick one to change, reorder or delete it (each change one undo step). -->
|
|
21
|
+
{#if stickers.length && layerId}
|
|
22
|
+
<div class={`${panel} ${className}`} data-testid="sticker-panel">
|
|
23
|
+
<span class="font-medium text-slate-300">Stickers ({stickers.length})</span>
|
|
24
|
+
<div class="flex max-h-24 flex-wrap gap-1 overflow-y-auto" role="listbox" aria-label="Stickers">
|
|
25
|
+
{#each stickers as s (s.id)}
|
|
26
|
+
<button type="button" role="option" aria-selected={s.id === picked} title={`${STAMP_LABELS[s.stamp]} ${s.id}`} class={`h-5 w-5 rounded ring-1 ${s.id === picked ? 'ring-indigo-400' : 'ring-slate-700'}`} style={`background: ${s.color}`} onclick={() => (picked = s.id)}></button>
|
|
27
|
+
{/each}
|
|
28
|
+
</div>
|
|
29
|
+
{#if sel}
|
|
30
|
+
<div class="flex flex-col gap-1.5" data-testid="sticker-edit">
|
|
31
|
+
<label class="flex items-center justify-between gap-2">
|
|
32
|
+
<span class="text-slate-400">Stamp</span>
|
|
33
|
+
<select aria-label="Sticker stamp" value={sel.stamp} onchange={(e) => { const stamp = e.currentTarget.value as StampKind; run(() => controller.updateSticker(layerId, sel.id, { stamp })); }} class="rounded bg-slate-800 px-1 py-0.5">
|
|
34
|
+
{#each STAMP_KINDS as k (k)}<option value={k}>{STAMP_LABELS[k]}</option>{/each}
|
|
35
|
+
</select>
|
|
36
|
+
</label>
|
|
37
|
+
{#each STICKER_SLIDERS as c (c.path)}
|
|
38
|
+
<PaintSlider control={c} value={readPath(sel, c.path)} oncommit={(v) => run(() => controller.updateSticker(layerId, sel.id, { [c.path]: v }))} />
|
|
39
|
+
{/each}
|
|
40
|
+
<label class="flex items-center justify-between">
|
|
41
|
+
<span class="text-slate-400">Colour</span>
|
|
42
|
+
<input type="color" aria-label="Sticker colour" value={sel.color} onchange={(e) => { const color = e.currentTarget.value; run(() => controller.updateSticker(layerId, sel.id, { color })); }} class="h-6 w-10 rounded bg-transparent" />
|
|
43
|
+
</label>
|
|
44
|
+
<div class="flex flex-wrap gap-1">
|
|
45
|
+
<button type="button" class={button} onclick={() => run(() => controller.arrangeStickers(layerId, [sel.id], 'front'))}>To front</button>
|
|
46
|
+
<button type="button" class={button} onclick={() => run(() => controller.arrangeStickers(layerId, [sel.id], 'back'))}>To back</button>
|
|
47
|
+
<button type="button" class={button} onclick={() => run(() => { controller.removeStickers(layerId, [sel.id]); picked = null; })}>Delete</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
{/if}
|
|
51
|
+
{#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
|
|
52
|
+
</div>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: PaintEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const StickerPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type StickerPanel = ReturnType<typeof StickerPanel>;
|
|
8
|
+
export default StickerPanel;
|
|
9
|
+
//# sourceMappingURL=StickerPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StickerPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/StickerPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwD,KAAK,qBAAqB,EAAkB,MAAM,+BAA+B,CAAC;AAK/I,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqDH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { WET_FLOW_KINDS, WET_PARAM_SLIDERS, WET_SPEED_SLIDER, WET_TILT_SLIDERS, readPath, type PaintEditorController, type WetFlowKind } from '@pixerate/editor/paint-editor';
|
|
3
|
+
import PaintSlider from './PaintSlider.svelte';
|
|
4
|
+
import { attempt, button, chip, paintEditorState, panel } from './paintEditorRune.svelte.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
controller: PaintEditorController;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
let { controller, class: className = '' }: Props = $props();
|
|
11
|
+
const editor = paintEditorState(() => controller);
|
|
12
|
+
const wet = $derived(editor.current.wet);
|
|
13
|
+
const layerId = $derived(editor.current.document.activeLayerId);
|
|
14
|
+
const info = $derived.by(() => {
|
|
15
|
+
void editor.current;
|
|
16
|
+
return layerId ? controller.wetLayerInfo(layerId) : null;
|
|
17
|
+
});
|
|
18
|
+
const backend = $derived.by(() => {
|
|
19
|
+
void editor.current;
|
|
20
|
+
return controller.wetState();
|
|
21
|
+
});
|
|
22
|
+
let error = $state<string | null>(null);
|
|
23
|
+
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
24
|
+
const FLOW_LABELS: Record<WetFlowKind, string> = { none: 'None', linear: 'One way', swirl: 'Swirl', radial: 'Outwards', turbulent: 'Eddies' };
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<!-- Wet media: status, Dry now, Pause, the water overlay, speed, Wet paper, tilt, the active layer's wet settings and flow map, and the kernel in use. -->
|
|
28
|
+
<div class={`${panel} ${className}`} data-testid="wet-panel">
|
|
29
|
+
<div class="flex items-center justify-between">
|
|
30
|
+
<span class={wet.wet ? 'text-sky-300' : 'text-slate-400'} data-testid="wet-status">{wet.wet ? (wet.paused ? 'Wet (paused)' : 'Wet') : 'Dry'}</span>
|
|
31
|
+
<span class="text-[10px] text-slate-500">step {wet.clock}</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="flex flex-wrap gap-1">
|
|
34
|
+
<button type="button" class={button} disabled={!wet.layerIds.length} onclick={() => run(async () => (await controller.wetIdle(), controller.dryPaint()))}>Dry now</button>
|
|
35
|
+
<button type="button" class={chip(wet.paused)} aria-pressed={wet.paused} onclick={() => controller.setWetPaused(!wet.paused)}>{wet.paused ? 'Resume' : 'Pause'}</button>
|
|
36
|
+
<button type="button" class={chip(wet.overlay)} aria-pressed={wet.overlay} onclick={() => controller.setWaterOverlay(!wet.overlay)}>Water</button>
|
|
37
|
+
<button type="button" class={chip(wet.instantDry)} aria-pressed={wet.instantDry} title="Each wet stroke dries as soon as it ends" onclick={() => controller.setInstantDry(!wet.instantDry)}>Instant dry</button>
|
|
38
|
+
<button type="button" class={button} disabled={!layerId} onclick={() => run(async () => (await controller.wetIdle(), controller.wetPaper({})))}>Wet paper</button>
|
|
39
|
+
</div>
|
|
40
|
+
<PaintSlider control={WET_SPEED_SLIDER} value={wet.speed} onchange={(v) => controller.setWetSpeed(v)} />
|
|
41
|
+
{#each WET_TILT_SLIDERS as c (c.path)}
|
|
42
|
+
<PaintSlider control={c} value={readPath(wet.tilt, c.path)} oncommit={(v) => run(async () => (await controller.wetIdle(), controller.setCanvasTilt({ [c.path]: v })))} />
|
|
43
|
+
{/each}
|
|
44
|
+
{#if info && layerId}
|
|
45
|
+
<div class="flex flex-col gap-1.5" data-testid="wet-layer">
|
|
46
|
+
<span class="font-medium text-slate-300">This layer</span>
|
|
47
|
+
{#each WET_PARAM_SLIDERS as c (c.path)}
|
|
48
|
+
<PaintSlider control={c} value={readPath(info.params, c.path)} oncommit={(v) => run(async () => (await controller.wetIdle(), controller.setWetParams(layerId, { [c.path]: v })))} />
|
|
49
|
+
{/each}
|
|
50
|
+
<label class="flex items-center justify-between gap-2">
|
|
51
|
+
<span class="text-slate-400">Flow map</span>
|
|
52
|
+
<select aria-label="Flow map" value={info.flow.kind} onchange={(e) => { const kind = e.currentTarget.value as WetFlowKind; run(async () => (await controller.wetIdle(), controller.setFlowMap(layerId, { kind }))); }} class="rounded bg-slate-800 px-1 py-0.5">
|
|
53
|
+
{#each WET_FLOW_KINDS as k (k)}<option value={k}>{FLOW_LABELS[k]}</option>{/each}
|
|
54
|
+
</select>
|
|
55
|
+
</label>
|
|
56
|
+
{#if info.flow.kind !== 'none'}
|
|
57
|
+
<PaintSlider control={{ path: 'strength', label: 'Flow strength', min: 0, max: 1, step: 0.01 }} value={info.flow.strength} oncommit={(v) => run(async () => (await controller.wetIdle(), controller.setFlowMap(layerId, { strength: v })))} />
|
|
58
|
+
{/if}
|
|
59
|
+
</div>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if backend.message}<p class="text-[10px] text-amber-300" data-testid="wet-message">{backend.message}</p>{/if}
|
|
62
|
+
{#if backend.backend}
|
|
63
|
+
<p class="text-[10px] text-slate-500">{backend.backend.kind === 'wasm-threads' ? `WebAssembly, ${backend.backend.threads} threads` : backend.backend.kind === 'wasm' ? 'WebAssembly, one thread' : 'JavaScript'}</p>
|
|
64
|
+
{/if}
|
|
65
|
+
{#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
|
|
66
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: PaintEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const WetPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type WetPanel = ReturnType<typeof WetPanel>;
|
|
8
|
+
export default WetPanel;
|
|
9
|
+
//# sourceMappingURL=WetPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WetPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/WetPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAmF,KAAK,qBAAqB,EAAoB,MAAM,+BAA+B,CAAC;AAK5K,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkEH,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -6,6 +6,8 @@ import type BrushPanelComponent from './BrushPanel.svelte';
|
|
|
6
6
|
import type PaperPanelComponent from './PaperPanel.svelte';
|
|
7
7
|
import type ColorPanelComponent from './ColorPanel.svelte';
|
|
8
8
|
import type PaintLayersPanelComponent from './PaintLayersPanel.svelte';
|
|
9
|
+
import type WetPanelComponent from './WetPanel.svelte';
|
|
10
|
+
import type StickerPanelComponent from './StickerPanel.svelte';
|
|
9
11
|
export { default as PaintEditor } from './PaintEditor.svelte';
|
|
10
12
|
export { default as PaintCanvas } from './PaintCanvas.svelte';
|
|
11
13
|
export { default as PaintToolbar } from './PaintToolbar.svelte';
|
|
@@ -13,6 +15,8 @@ export { default as BrushPanel } from './BrushPanel.svelte';
|
|
|
13
15
|
export { default as PaperPanel } from './PaperPanel.svelte';
|
|
14
16
|
export { default as ColorPanel } from './ColorPanel.svelte';
|
|
15
17
|
export { default as PaintLayersPanel } from './PaintLayersPanel.svelte';
|
|
18
|
+
export { default as WetPanel } from './WetPanel.svelte';
|
|
19
|
+
export { default as StickerPanel } from './StickerPanel.svelte';
|
|
16
20
|
export { default as PaintSlider } from './PaintSlider.svelte';
|
|
17
21
|
export { default as PaintPresenceLayer } from './PaintPresenceLayer.svelte';
|
|
18
22
|
export { createPaintDirector, createReactivePaintEditor, paintEditorState } from './paintEditorRune.svelte.js';
|
|
@@ -23,4 +27,6 @@ export type BrushPanelProps = ComponentProps<typeof BrushPanelComponent>;
|
|
|
23
27
|
export type PaperPanelProps = ComponentProps<typeof PaperPanelComponent>;
|
|
24
28
|
export type ColorPanelProps = ComponentProps<typeof ColorPanelComponent>;
|
|
25
29
|
export type PaintLayersPanelProps = ComponentProps<typeof PaintLayersPanelComponent>;
|
|
30
|
+
export type WetPanelProps = ComponentProps<typeof WetPanelComponent>;
|
|
31
|
+
export type StickerPanelProps = ComponentProps<typeof StickerPanelComponent>;
|
|
26
32
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/paint-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,yBAAyB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/paint-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,yBAAyB,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,iBAAiB,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE/G,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACzE,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,yBAAyB,CAAC,CAAC;AACrF,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
@@ -5,6 +5,8 @@ export { default as BrushPanel } from './BrushPanel.svelte';
|
|
|
5
5
|
export { default as PaperPanel } from './PaperPanel.svelte';
|
|
6
6
|
export { default as ColorPanel } from './ColorPanel.svelte';
|
|
7
7
|
export { default as PaintLayersPanel } from './PaintLayersPanel.svelte';
|
|
8
|
+
export { default as WetPanel } from './WetPanel.svelte';
|
|
9
|
+
export { default as StickerPanel } from './StickerPanel.svelte';
|
|
8
10
|
export { default as PaintSlider } from './PaintSlider.svelte';
|
|
9
11
|
export { default as PaintPresenceLayer } from './PaintPresenceLayer.svelte';
|
|
10
12
|
export { createPaintDirector, createReactivePaintEditor, paintEditorState } from './paintEditorRune.svelte.js';
|
|
@@ -466,6 +466,14 @@
|
|
|
466
466
|
{/if}
|
|
467
467
|
</g>
|
|
468
468
|
{/if}
|
|
469
|
+
{#each overlay.shading as sh (sh.nodeId)}
|
|
470
|
+
<g data-vector-shading={sh.kind}>
|
|
471
|
+
{#each sh.lines as d, i (i)}<path {d} stroke={accentColor} stroke-width="1" opacity="0.7" />{/each}
|
|
472
|
+
{#each sh.points as p (p.index)}
|
|
473
|
+
<circle data-vector-shading-point={p.index} data-selected={p.selected || undefined} cx={p.x} cy={p.y} r={p.selected ? 6 : 4.5} fill={p.color} stroke={p.selected ? accentColor : '#ffffff'} stroke-width={p.selected ? 2 : 1.5} />
|
|
474
|
+
{/each}
|
|
475
|
+
</g>
|
|
476
|
+
{/each}
|
|
469
477
|
{#each overlay.trackingPoints as t, i (i)}
|
|
470
478
|
<path data-vector-tracking-point="" d="M{t.x - 5} {t.y}H{t.x + 5}M{t.x} {t.y - 5}V{t.y + 5}" stroke={guideColor} stroke-width="1.5" />
|
|
471
479
|
{/each}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VectorCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorCanvas.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,EASH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AASjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"VectorCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorCanvas.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,EASH,KAAK,sBAAsB,EAC5B,MAAM,yBAAyB,CAAC;AASjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2aH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { isVectorFreeformGradient, isVectorMeshGradient, vectorPaintToHex, type VectorEditorController, type VectorPathNode } from '@pixerate/editor/vector';
|
|
3
|
+
import { vectorStateOf } from './subscribe.svelte.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gradient meshes and freeform gradients of the selected path: make a rows × columns mesh or a
|
|
7
|
+
* freeform gradient, and edit the point picked with the Gradient tool (colour, opacity, and a
|
|
8
|
+
* freeform point's spread, or remove it). Each change is one undo step.
|
|
9
|
+
*/
|
|
10
|
+
interface Props {
|
|
11
|
+
controller: VectorEditorController;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { controller, class: className = '' }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const store = vectorStateOf(() => controller);
|
|
18
|
+
const view = $derived(store.current);
|
|
19
|
+
let rows = $state(2);
|
|
20
|
+
let columns = $state(2);
|
|
21
|
+
const path = $derived(view.selection.map((id) => controller.getNode(id)).find((n): n is VectorPathNode => n?.type === 'path') ?? null);
|
|
22
|
+
const mesh = $derived(path && isVectorMeshGradient(path.fill) ? path.fill : null);
|
|
23
|
+
const free = $derived(path && isVectorFreeformGradient(path.fill) ? path.fill : null);
|
|
24
|
+
const sel = $derived(path && view.shadingPoint?.nodeId === path.id ? view.shadingPoint : null);
|
|
25
|
+
const color = $derived(sel ? (mesh ? mesh.colors[sel.index] : free?.points[sel.index]?.color) : undefined);
|
|
26
|
+
const opacity = $derived(sel ? (mesh ? (mesh.opacities?.[sel.index] ?? 1) : (free?.points[sel.index]?.opacity ?? 1)) : 1);
|
|
27
|
+
const spread = $derived(sel && free ? (free.points[sel.index]?.spread ?? 0) : 0);
|
|
28
|
+
|
|
29
|
+
function edit(patch: { color?: string; opacity?: number; spread?: number }, merge?: string) {
|
|
30
|
+
if (!sel || !path) return;
|
|
31
|
+
if (mesh) controller.setMeshPoint(path.id, sel.index, patch, merge ? { merge } : {});
|
|
32
|
+
else controller.setFreeformPoint(path.id, sel.index, patch, merge ? { merge } : {});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const buttonClass =
|
|
36
|
+
'inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40';
|
|
37
|
+
const numberClass = 'h-7 w-12 rounded border border-border bg-background px-1.5 text-xs';
|
|
38
|
+
const clamp = (v: string) => Math.max(1, Math.min(32, parseInt(v, 10) || 1));
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
{#if path}
|
|
42
|
+
<div class="space-y-2 {className}" data-vector-shading-panel={path.id} aria-label="Mesh and freeform gradient">
|
|
43
|
+
<div class="flex flex-wrap items-center gap-1">
|
|
44
|
+
<span class="w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Shading</span>
|
|
45
|
+
<input type="number" min="1" max="32" aria-label="Mesh rows" title="Mesh rows" class={numberClass} value={rows} oninput={(e) => (rows = clamp(e.currentTarget.value))} />
|
|
46
|
+
<span class="text-xs text-muted-foreground">×</span>
|
|
47
|
+
<input type="number" min="1" max="32" aria-label="Mesh columns" title="Mesh columns" class={numberClass} value={columns} oninput={(e) => (columns = clamp(e.currentTarget.value))} />
|
|
48
|
+
<button type="button" class={buttonClass} data-vector-shading-action="mesh" title="Create Gradient Mesh: smooth patches whose points blend colours (edit them with the Gradient tool)" onclick={() => controller.setMeshGradient({ rows, columns }, { ids: [path.id] })}>Mesh</button>
|
|
49
|
+
<button type="button" class={buttonClass} data-vector-shading-action="freeform" title="Freeform Gradient: colour points blended across the shape (click inside with the Gradient tool to add one)" onclick={() => controller.setFreeformGradient({}, { ids: [path.id] })}>Freeform</button>
|
|
50
|
+
</div>
|
|
51
|
+
{#if (mesh || free) && !sel}
|
|
52
|
+
<p class="text-[11px] text-muted-foreground">Gradient tool (G): drag points; {free ? 'click inside to add one, Alt-click to remove.' : 'pick one to colour it.'}</p>
|
|
53
|
+
{/if}
|
|
54
|
+
{#if sel}
|
|
55
|
+
<div class="flex flex-wrap items-center gap-2" data-vector-shading-point={sel.index}>
|
|
56
|
+
<span class="w-16 text-xs text-foreground">Point {sel.index}</span>
|
|
57
|
+
<input type="color" aria-label="Point colour" class="h-7 w-10 cursor-pointer rounded border border-border bg-background" value={vectorPaintToHex(color ?? '#000000') ?? '#000000'} oninput={(e) => edit({ color: e.currentTarget.value }, 'shading-color')} />
|
|
58
|
+
<input type="range" min="0" max="1" step="0.05" aria-label="Point opacity" title="Opacity" class="h-4 w-20 accent-primary" value={opacity} oninput={(e) => edit({ opacity: parseFloat(e.currentTarget.value) }, 'shading-opacity')} />
|
|
59
|
+
{#if free}
|
|
60
|
+
<input type="range" min="0" max="1" step="0.05" aria-label="Point spread" title="Spread: pure colour round the point" class="h-4 w-20 accent-primary" value={spread} oninput={(e) => edit({ spread: parseFloat(e.currentTarget.value) }, 'shading-spread')} />
|
|
61
|
+
<button type="button" class={buttonClass} aria-label="Remove point" onclick={() => controller.removeFreeformPoint(path.id, sel.index)}>Remove</button>
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type VectorEditorController } from '@pixerate/editor/vector';
|
|
2
|
+
/**
|
|
3
|
+
* Gradient meshes and freeform gradients of the selected path: make a rows × columns mesh or a
|
|
4
|
+
* freeform gradient, and edit the point picked with the Gradient tool (colour, opacity, and a
|
|
5
|
+
* freeform point's spread, or remove it). Each change is one undo step.
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
controller: VectorEditorController;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const VectorShadingPanel: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type VectorShadingPanel = ReturnType<typeof VectorShadingPanel>;
|
|
13
|
+
export default VectorShadingPanel;
|
|
14
|
+
//# sourceMappingURL=VectorShadingPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VectorShadingPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorShadingPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoE,KAAK,sBAAsB,EAAuB,MAAM,yBAAyB,CAAC;AAI3J;;;;GAIG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6DH,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
import VectorConstraintsPanel from './VectorConstraintsPanel.svelte';
|
|
27
27
|
import VectorFramePanel from './VectorFramePanel.svelte';
|
|
28
28
|
import VectorConnectorPanel from './VectorConnectorPanel.svelte';
|
|
29
|
+
import VectorShadingPanel from './VectorShadingPanel.svelte';
|
|
30
|
+
import VectorTracePanel from './VectorTracePanel.svelte';
|
|
29
31
|
import VectorGradientEditor from './VectorGradientEditor.svelte';
|
|
30
32
|
|
|
31
33
|
interface Props {
|
|
@@ -253,6 +255,8 @@
|
|
|
253
255
|
{/if}
|
|
254
256
|
<VectorFramePanel {controller} />
|
|
255
257
|
<VectorConnectorPanel {controller} />
|
|
258
|
+
<VectorShadingPanel {controller} />
|
|
259
|
+
<VectorTracePanel {controller} />
|
|
256
260
|
<VectorConstraintsPanel {controller} />
|
|
257
261
|
<VectorSymbolsPanel {controller} />
|
|
258
262
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VectorStylePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorStylePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAYH,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"VectorStylePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorStylePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAYH,KAAK,sBAAsB,EAG5B,MAAM,yBAAyB,CAAC;AAiBjC,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwLH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { VectorEditorController } from '@pixerate/editor/vector';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Image Trace: pick an image file and trace it into paths, in one colour (dark areas) or a
|
|
6
|
+
* stacked path per colour. The traced group goes on the artboard at its width. One undo step.
|
|
7
|
+
*/
|
|
8
|
+
interface Props {
|
|
9
|
+
controller: VectorEditorController;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { controller, class: className = '' }: Props = $props();
|
|
14
|
+
|
|
15
|
+
let input: HTMLInputElement | undefined = $state();
|
|
16
|
+
let mode: 'threshold' | 'color' = $state('threshold');
|
|
17
|
+
let colors = $state(6);
|
|
18
|
+
let message: string | null = $state(null);
|
|
19
|
+
|
|
20
|
+
/** Decodes an image file in the browser into RGBA pixels (at most `max` pixels, scaled down to fit). */
|
|
21
|
+
async function filePixels(file: File, max = 1_000_000) {
|
|
22
|
+
const bitmap = await createImageBitmap(file);
|
|
23
|
+
const k = Math.min(1, Math.sqrt(max / (bitmap.width * bitmap.height)));
|
|
24
|
+
const width = Math.max(1, Math.round(bitmap.width * k));
|
|
25
|
+
const height = Math.max(1, Math.round(bitmap.height * k));
|
|
26
|
+
const canvas = document.createElement('canvas');
|
|
27
|
+
canvas.width = width;
|
|
28
|
+
canvas.height = height;
|
|
29
|
+
const ctx = canvas.getContext('2d')!;
|
|
30
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
31
|
+
return { width, height, data: ctx.getImageData(0, 0, width, height).data };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function trace(file: File) {
|
|
35
|
+
try {
|
|
36
|
+
const pixels = await filePixels(file);
|
|
37
|
+
const doc = controller.getState().document;
|
|
38
|
+
const width = Math.min(doc.width, pixels.width);
|
|
39
|
+
const r = controller.traceImage(pixels, { mode, colors, width, name: file.name.replace(/\.[^.]+$/, '') || 'Image Trace' });
|
|
40
|
+
message = r.ok ? null : r.message;
|
|
41
|
+
} catch {
|
|
42
|
+
message = 'That file could not be read as an image.';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const buttonClass =
|
|
47
|
+
'inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40';
|
|
48
|
+
const selectClass = 'h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs';
|
|
49
|
+
const numberClass = 'h-7 w-12 rounded border border-border bg-background px-1.5 text-xs';
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<div class="space-y-1 {className}" data-vector-trace-panel="" aria-label="Image trace">
|
|
53
|
+
<div class="flex items-center gap-1">
|
|
54
|
+
<span class="w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Trace</span>
|
|
55
|
+
<select aria-label="Trace mode" class={selectClass} bind:value={mode}>
|
|
56
|
+
<option value="threshold">Black & white</option>
|
|
57
|
+
<option value="color">Colours</option>
|
|
58
|
+
</select>
|
|
59
|
+
{#if mode === 'color'}
|
|
60
|
+
<input type="number" min="2" max="32" aria-label="Trace colours" title="Colours" class={numberClass} value={colors} oninput={(e) => (colors = Math.max(2, Math.min(32, parseInt(e.currentTarget.value, 10) || 2)))} />
|
|
61
|
+
{/if}
|
|
62
|
+
<button type="button" class={buttonClass} data-vector-trace-action="" title="Image Trace: pick an image to turn into paths" onclick={() => input?.click()}>Image…</button>
|
|
63
|
+
<input
|
|
64
|
+
bind:this={input}
|
|
65
|
+
type="file"
|
|
66
|
+
accept="image/*"
|
|
67
|
+
hidden
|
|
68
|
+
onchange={(e) => {
|
|
69
|
+
const file = e.currentTarget.files?.[0];
|
|
70
|
+
e.currentTarget.value = '';
|
|
71
|
+
if (file) void trace(file);
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
{#if message}<p class="text-[11px] text-red-600">{message}</p>{/if}
|
|
76
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { VectorEditorController } from '@pixerate/editor/vector';
|
|
2
|
+
/**
|
|
3
|
+
* Image Trace: pick an image file and trace it into paths, in one colour (dark areas) or a
|
|
4
|
+
* stacked path per colour. The traced group goes on the artboard at its width. One undo step.
|
|
5
|
+
*/
|
|
6
|
+
interface Props {
|
|
7
|
+
controller: VectorEditorController;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const VectorTracePanel: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type VectorTracePanel = ReturnType<typeof VectorTracePanel>;
|
|
12
|
+
export default VectorTracePanel;
|
|
13
|
+
//# sourceMappingURL=VectorTracePanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VectorTracePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/vector/VectorTracePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGpE;;;GAGG;AACH,UAAU,KAAK;IACb,UAAU,EAAE,sBAAsB,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkEH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
package/dist/vector/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { default as VectorSymbolsPanel } from "./VectorSymbolsPanel.svelte";
|
|
|
14
14
|
export { default as VectorConstraintsPanel } from "./VectorConstraintsPanel.svelte";
|
|
15
15
|
export { default as VectorFramePanel } from "./VectorFramePanel.svelte";
|
|
16
16
|
export { default as VectorConnectorPanel } from "./VectorConnectorPanel.svelte";
|
|
17
|
+
export { default as VectorShadingPanel } from "./VectorShadingPanel.svelte";
|
|
18
|
+
export { default as VectorTracePanel } from "./VectorTracePanel.svelte";
|
|
17
19
|
export { default as VectorTextElement } from "./VectorTextElement.svelte";
|
|
18
20
|
export { default as VectorMaskedNodes } from "./VectorMaskedNodes.svelte";
|
|
19
21
|
export { default as VectorSceneNodes } from "./VectorSceneNodes.svelte";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,cAAc,gCAAgC,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,cAAc,aAAa,CAAC;AAE5B,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vector/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,cAAc,gCAAgC,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,cAAc,aAAa,CAAC;AAE5B,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/vector/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export { default as VectorSymbolsPanel } from "./VectorSymbolsPanel.svelte";
|
|
|
11
11
|
export { default as VectorConstraintsPanel } from "./VectorConstraintsPanel.svelte";
|
|
12
12
|
export { default as VectorFramePanel } from "./VectorFramePanel.svelte";
|
|
13
13
|
export { default as VectorConnectorPanel } from "./VectorConnectorPanel.svelte";
|
|
14
|
+
export { default as VectorShadingPanel } from "./VectorShadingPanel.svelte";
|
|
15
|
+
export { default as VectorTracePanel } from "./VectorTracePanel.svelte";
|
|
14
16
|
export { default as VectorTextElement } from "./VectorTextElement.svelte";
|
|
15
17
|
export { default as VectorMaskedNodes } from "./VectorMaskedNodes.svelte";
|
|
16
18
|
export { default as VectorSceneNodes } from "./VectorSceneNodes.svelte";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.1",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"@tiptap/extension-bubble-menu": "^2.11.5",
|
|
140
140
|
"@tiptap/extension-placeholder": "^2.11.5",
|
|
141
141
|
"@tiptap/starter-kit": "^2.11.5",
|
|
142
|
-
"@pixerate/editor": "0.
|
|
142
|
+
"@pixerate/editor": "0.117.0"
|
|
143
143
|
},
|
|
144
144
|
"peerDependencies": {
|
|
145
145
|
"@dagrejs/dagre": "^1.1.4",
|