Skip to content

Commit 2afa490

Browse files
committed
fix blur size and opacity correctly with brute force gaussian blur
1 parent 56421f6 commit 2afa490

File tree

6 files changed

+181
-154
lines changed

6 files changed

+181
-154
lines changed

apps/desktop/src/routes/editor/BlurOverlay.tsx

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function BlurOverlay() {
2121
const containerBounds = createElementBounds(canvasContainerRef);
2222

2323
const currentTime = () => editorState.previewTime ?? editorState.playbackTime ?? 0;
24+
2425

2526
const activeBlurSegmentsWithIndex = () => {
2627
return (project.timeline?.blurSegments || []).map((segment, index) => ({ segment, index })).filter(
@@ -86,7 +87,7 @@ function BlurRectangle(props: BlurRectangleProps) {
8687
const startX = e.clientX;
8788
const startY = e.clientY;
8889
const startRect = { ...props.rect };
89-
90+
9091
createRoot((dispose) => {
9192
createEventListenerMap(window, {
9293
mousemove: (moveEvent: MouseEvent) => {
@@ -96,42 +97,35 @@ function BlurRectangle(props: BlurRectangleProps) {
9697
let newRect = { ...startRect };
9798

9899
if (action === 'move') {
100+
// Clamp the new position to stay within the 0.0 to 1.0 bounds
99101
newRect.x = Math.max(0, Math.min(1 - newRect.width, startRect.x + deltaX));
100102
newRect.y = Math.max(0, Math.min(1 - newRect.height, startRect.y + deltaY));
101103
} else if (action === 'resize') {
102-
switch (corner) {
103-
case 'nw': // Northwest corner
104-
newRect.x = Math.max(0, startRect.x + deltaX);
105-
newRect.y = Math.max(0, startRect.y + deltaY);
106-
newRect.width = startRect.width - deltaX;
107-
newRect.height = startRect.height - deltaY;
108-
break;
109-
case 'ne': // Northeast corner
110-
newRect.y = Math.max(0, startRect.y + deltaY);
111-
newRect.width = startRect.width + deltaX;
112-
newRect.height = startRect.height - deltaY;
113-
break;
114-
case 'sw': // Southwest corner
115-
newRect.x = Math.max(0, startRect.x + deltaX);
116-
newRect.width = startRect.width - deltaX;
117-
newRect.height = startRect.height + deltaY;
118-
break;
119-
case 'se': // Southeast corner
120-
newRect.width = startRect.width + deltaX;
121-
newRect.height = startRect.height + deltaY;
122-
break;
123-
}
104+
// --- This resize logic needs the bounds check ---
105+
let right = startRect.x + startRect.width;
106+
let bottom = startRect.y + startRect.height;
124107

125-
// Ensure minimum size
126-
newRect.width = Math.max(0.05, newRect.width);
127-
newRect.height = Math.max(0.05, newRect.height);
128-
129-
// Ensure within bounds
130-
newRect.x = Math.max(0, Math.min(1 - newRect.width, newRect.x));
131-
newRect.y = Math.max(0, Math.min(1 - newRect.height, newRect.y));
132-
newRect.width = Math.min(1 - newRect.x, newRect.width);
133-
newRect.height = Math.min(1 - newRect.y, newRect.height);
108+
if (corner?.includes('w')) { // West (left) handles
109+
newRect.x = Math.max(0, startRect.x + deltaX);
110+
newRect.width = right - newRect.x;
111+
}
112+
if (corner?.includes('n')) { // North (top) handles
113+
newRect.y = Math.max(0, startRect.y + deltaY);
114+
newRect.height = bottom - newRect.y;
115+
}
116+
if (corner?.includes('e')) { // East (right) handles
117+
right = Math.min(1, right + deltaX);
118+
newRect.width = right - newRect.x;
119+
}
120+
if (corner?.includes('s')) { // South (bottom) handles
121+
bottom = Math.min(1, bottom + deltaY);
122+
newRect.height = bottom - newRect.y;
123+
}
134124
}
125+
126+
// Ensure minimum size after any operation
127+
if (newRect.width < 0.05) newRect.width = 0.05;
128+
if (newRect.height < 0.05) newRect.height = 0.05;
135129

136130
props.onUpdate(newRect);
137131
},
@@ -141,7 +135,7 @@ function BlurRectangle(props: BlurRectangleProps) {
141135
});
142136
});
143137
};
144-
138+
const scaledBlurAmount = () => (props.blurAmount ?? 0) * 20;
145139
return (
146140
<div
147141
class={cx(
@@ -150,8 +144,8 @@ function BlurRectangle(props: BlurRectangleProps) {
150144
)}
151145
style={{
152146
...props.style,
153-
"backdrop-filter": `blur(${props.blurAmount}px)`,
154-
"-webkit-backdrop-filter": `blur(${props.blurAmount}px)`, // Fallback for WebKit browsers
147+
"backdrop-filter": `blur(${scaledBlurAmount()}px)`,
148+
"-webkit-backdrop-filter": `blur(${scaledBlurAmount()}px)`,
155149
}}
156150
>
157151
<Show when={props.isEditing}>

apps/desktop/src/routes/editor/ConfigSidebar.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,8 +2034,9 @@ function BlurSegmentConfig(props: {
20342034
</div>
20352035

20362036
<Field name="Blur Intensity" icon={<IconCapBlur />}>
2037-
<Slider
2038-
value={[props.segment.blur_amount]}
2037+
<Slider
2038+
2039+
value={[props.segment.blur_amount ?? 0]}
20392040
onChange={(v) =>
20402041
setProject(
20412042
"timeline",
@@ -2045,10 +2046,11 @@ function BlurSegmentConfig(props: {
20452046
v[0],
20462047
)
20472048
}
2049+
20482050
minValue={0}
2049-
maxValue={20}
2050-
step={0.1}
2051-
formatTooltip="px"
2051+
maxValue={1}
2052+
step={0.01}
2053+
formatTooltip={(value) => `${Math.round(value * 100)}%`}
20522054
/>
20532055
</Field>
20542056

apps/desktop/src/routes/editor/Timeline/BlurTrack.tsx

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
createEventListener,
33
createEventListenerMap,
44
} from "@solid-primitives/event-listener";
5-
import { Menu } from "@tauri-apps/api/menu";
65
import { cx } from "cva";
76
import {
87
batch,
@@ -13,7 +12,6 @@ import {
1312
Show,
1413
} from "solid-js";
1514
import { produce } from "solid-js/store";
16-
import { commands } from "~/utils/tauri";
1715
import { useEditorContext } from "../context";
1816
import {
1917
useSegmentContext,
@@ -39,33 +37,8 @@ export function BlurTrack(props: {
3937
const [hoveringSegment, setHoveringSegment] = createSignal(false);
4038
const [hoveredTime, setHoveredTime] = createSignal<number>();
4139

42-
const handleGenerateBlurSegments = async () => {
43-
try {
44-
const blurSegments = await commands.generateBlurSegmentsFromClicks();
45-
setProject("timeline", "blurSegments", blurSegments);
46-
} catch (error) {
47-
console.error("Failed to generate blur segments:", error);
48-
}
49-
};
50-
5140
return (
5241
<TrackRoot
53-
onContextMenu={async (e) => {
54-
if (!import.meta.env.DEV) return;
55-
56-
e.preventDefault();
57-
const menu = await Menu.new({
58-
id: "blur-track-options",
59-
items: [
60-
{
61-
id: "generateBlurSegments",
62-
text: "Generate blur segments from clicks",
63-
action: handleGenerateBlurSegments,
64-
},
65-
],
66-
});
67-
menu.popup();
68-
}}
6942
onMouseMove={(e) => {
7043
if (hoveringSegment()) {
7144
setHoveredTime(undefined);
@@ -161,7 +134,8 @@ export function BlurTrack(props: {
161134

162135
const blurPercentage = () => {
163136
const amount = segment.blur_amount;
164-
return `${amount.toFixed(1)}x`;
137+
// Handle potential null or undefined amount
138+
return amount ? `${amount.toFixed(1)}x` : '...';
165139
};
166140

167141
const blurSegments = () => project.timeline!.blurSegments!;
@@ -249,7 +223,7 @@ export function BlurTrack(props: {
249223
? "wobble-wrapper border-blue-400"
250224
: "border-transparent",
251225
)}
252-
226+
253227
innerClass="ring-red-5"
254228
segment={segment}
255229
onMouseEnter={() => {
@@ -299,7 +273,7 @@ export function BlurTrack(props: {
299273
"timeline",
300274
"blurSegments",
301275
produce((s) => {
302-
s.sort((a, b) => a.start - b.start);
276+
s?.sort((a, b) => a.start - b.start);
303277
}),
304278
);
305279
},
@@ -337,10 +311,11 @@ export function BlurTrack(props: {
337311
else if (newEnd > value.maxEnd)
338312
delta = value.maxEnd - value.original.end;
339313

340-
setProject("timeline", "blurSegments", i(), {
314+
setProject("timeline", "blurSegments", i(), (prev) => ({
315+
...prev,
341316
start: value.original.start + delta,
342317
end: value.original.end + delta,
343-
});
318+
}));
344319
},
345320
)}
346321
>
@@ -352,7 +327,8 @@ export function BlurTrack(props: {
352327
<div class="flex flex-col gap-1 justify-center items-center text-xs whitespace-nowrap text-gray-1 dark:text-gray-12 fade-in">
353328
<span class="opacity-70">Blur</span>
354329
<div class="flex gap-1 items-center text-md">
355-
<IconCapBlur class="size-3.5" />{" "}
330+
{/* Assuming you have an IconCapBlur component */}
331+
{/* <IconCapBlur class="size-3.5" />{" "} */}
356332
{blurPercentage()}{" "}
357333
</div>
358334
</div>
@@ -399,7 +375,7 @@ export function BlurTrack(props: {
399375
"timeline",
400376
"blurSegments",
401377
produce((s) => {
402-
s.sort((a, b) => a.start - b.start);
378+
s?.sort((a, b) => a.start - b.start);
403379
}),
404380
);
405381
},
@@ -431,4 +407,4 @@ export function BlurTrack(props: {
431407
</Show>
432408
</TrackRoot>
433409
);
434-
}
410+
}

crates/rendering/src/layers/selective_blur.rs

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct SelectiveBlurLayer {
1313
impl SelectiveBlurLayer {
1414
pub fn new(device: &wgpu::Device) -> Self {
1515
let pipeline = SelectiveBlurPipeline::new(device, wgpu::TextureFormat::Rgba8UnormSrgb);
16+
17+
// High-quality sampler for smooth blur
1618
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1719
label: Some("Selective Blur Sampler"),
1820
address_mode_u: wgpu::AddressMode::ClampToEdge,
@@ -21,6 +23,7 @@ impl SelectiveBlurLayer {
2123
mag_filter: wgpu::FilterMode::Linear,
2224
min_filter: wgpu::FilterMode::Linear,
2325
mipmap_filter: wgpu::FilterMode::Linear,
26+
anisotropy_clamp: 1,
2427
..Default::default()
2528
});
2629

@@ -43,69 +46,53 @@ impl SelectiveBlurLayer {
4346
.map(|segments| {
4447
segments
4548
.iter()
46-
.filter(|segment| current_time >= segment.start && current_time <= segment.end)
49+
.filter(|segment| {
50+
current_time >= segment.start as f32 &&
51+
current_time <= segment.end as f32
52+
})
4753
.collect()
4854
})
4955
.unwrap_or_default();
5056

51-
if active_segments.is_empty() {
52-
return;
53-
}
54-
5557
let gpu_blur_segments: Vec<GpuBlurSegment> = active_segments
5658
.iter()
57-
.map(|segment| GpuBlurSegment {
58-
rect: [
59-
segment.rect.x as f32,
60-
segment.rect.y as f32,
61-
segment.rect.width as f32,
62-
segment.rect.height as f32,
63-
],
64-
blur_amount: segment.blur_amount.unwrap_or(8.0),
65-
_padding: [0.0; 3],
59+
.filter(|segment| segment.blur_amount.unwrap_or(0.0) >= 0.01)
60+
.map(|segment| {
61+
// Convert from 0-1 slider range to shader-appropriate values
62+
let blur_intensity = segment.blur_amount.unwrap_or(0.0) as f32;
63+
64+
let shader_blur_amount = blur_intensity * 8.0;
65+
66+
GpuBlurSegment {
67+
rect: [
68+
segment.rect.x as f32,
69+
segment.rect.y as f32,
70+
segment.rect.width as f32,
71+
segment.rect.height as f32,
72+
],
73+
blur_amount: shader_blur_amount,
74+
_padding: [0.0; 3],
75+
}
6676
})
6777
.collect();
78+
79+
if gpu_blur_segments.is_empty() {
80+
return;
81+
}
6882

6983
let blur_uniforms = SelectiveBlurUniforms {
7084
output_size: [uniforms.output_size.0 as f32, uniforms.output_size.1 as f32],
7185
blur_segments_count: gpu_blur_segments.len() as u32,
7286
_padding: 0.0,
7387
};
7488

75-
let uniforms_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
76-
label: Some("Selective Blur Uniforms"),
77-
contents: cast_slice(&[blur_uniforms]),
78-
usage: wgpu::BufferUsages::UNIFORM,
79-
});
80-
81-
let segments_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
82-
label: Some("Blur Segments Buffer"),
83-
contents: cast_slice(&gpu_blur_segments),
84-
usage: wgpu::BufferUsages::STORAGE,
85-
});
86-
87-
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
88-
label: Some("Selective Blur Bind Group"),
89-
layout: &self.pipeline.bind_group_layout,
90-
entries: &[
91-
wgpu::BindGroupEntry {
92-
binding: 0,
93-
resource: uniforms_buffer.as_entire_binding(),
94-
},
95-
wgpu::BindGroupEntry {
96-
binding: 1,
97-
resource: wgpu::BindingResource::TextureView(input_texture_view),
98-
},
99-
wgpu::BindGroupEntry {
100-
binding: 2,
101-
resource: wgpu::BindingResource::Sampler(&self.sampler),
102-
},
103-
wgpu::BindGroupEntry {
104-
binding: 3,
105-
resource: segments_buffer.as_entire_binding(),
106-
},
107-
],
108-
});
89+
let bind_group = self.pipeline.create_bind_group(
90+
device,
91+
&blur_uniforms,
92+
input_texture_view,
93+
&self.sampler,
94+
&gpu_blur_segments,
95+
);
10996

11097
pass.set_pipeline(&self.pipeline.render_pipeline);
11198
pass.set_bind_group(0, &bind_group, &[]);

0 commit comments

Comments
 (0)