mihirmithani commited on
Commit
f3aa2b6
·
1 Parent(s): 25e7739
src/hooks/useConvolution.ts CHANGED
@@ -26,11 +26,21 @@ export function useConvolution() {
26
  const generatorRef = useRef<Generator<ConvolutionStep> | null>(null)
27
  const isPausedRef = useRef(false)
28
  const stepModeRef = useRef(false)
 
29
 
30
  const kernel = selectedKernel?.matrix ?? customKernelMatrix
31
  const normalize = selectedKernel?.normalize ?? customKernelNormalize
32
  const padding = settings.padding
33
 
 
 
 
 
 
 
 
 
 
34
  const runConvolution = useCallback(async () => {
35
  if (!originalImageData || !kernel) return
36
 
@@ -54,26 +64,27 @@ export function useConvolution() {
54
  generatorRef.current = gen
55
 
56
  const processBatch = () => {
57
- if (isPausedRef.current || stepModeRef.current) {
58
  animationRef.current = requestAnimationFrame(processBatch)
59
  return
60
  }
61
 
62
- const batchSize = Math.max(1, Math.floor(1000 * settings.animationSpeed))
 
 
 
 
 
 
 
63
  let lastStep: ConvolutionStep | null = null
 
64
 
65
  for (let i = 0; i < batchSize; i++) {
66
  const result = gen.next()
67
  if (result.done) {
68
  if (lastStep && lastStep.x === -1) {
69
  // Final step with output image
70
- const outputImageData = new ImageData(
71
- new Uint8ClampedArray(originalImageData.data.length),
72
- originalImageData.width,
73
- originalImageData.height
74
- )
75
- // We need to reconstruct from the generator's final output
76
- // For now, use the direct convolution
77
  const output = convolveImage(originalImageData, kernel, padding, normalize)
78
  completeConvolution(output)
79
  }
@@ -81,6 +92,12 @@ export function useConvolution() {
81
  }
82
  lastStep = result.value
83
  updateConvolutionStep(result.value)
 
 
 
 
 
 
84
  }
85
 
86
  animationRef.current = requestAnimationFrame(processBatch)
@@ -102,6 +119,7 @@ export function useConvolution() {
102
  updateConvolutionProgress,
103
  completeConvolution,
104
  setConvolutionError,
 
105
  ])
106
 
107
  const pause = useCallback(() => {
 
26
  const generatorRef = useRef<Generator<ConvolutionStep> | null>(null)
27
  const isPausedRef = useRef(false)
28
  const stepModeRef = useRef(false)
29
+ const frameStartRef = useRef<number>(0)
30
 
31
  const kernel = selectedKernel?.matrix ?? customKernelMatrix
32
  const normalize = selectedKernel?.normalize ?? customKernelNormalize
33
  const padding = settings.padding
34
 
35
+ // Calculate optimal batch size for ~60fps (16.67ms per frame)
36
+ // Aim to spend ~8ms on computation, leaving ~8ms for rendering
37
+ const getBatchSize = useCallback(() => {
38
+ const baseStepsPerMs = 500 // approximate steps per ms on modern hardware
39
+ const targetComputeTime = 8 // ms
40
+ const speedMultiplier = settings.animationSpeed
41
+ return Math.max(1, Math.floor(baseStepsPerMs * targetComputeTime * speedMultiplier))
42
+ }, [settings.animationSpeed])
43
+
44
  const runConvolution = useCallback(async () => {
45
  if (!originalImageData || !kernel) return
46
 
 
64
  generatorRef.current = gen
65
 
66
  const processBatch = () => {
67
+ if (isPausedRef.current) {
68
  animationRef.current = requestAnimationFrame(processBatch)
69
  return
70
  }
71
 
72
+ if (stepModeRef.current) {
73
+ // In step mode, don't auto-advance
74
+ animationRef.current = requestAnimationFrame(processBatch)
75
+ return
76
+ }
77
+
78
+ frameStartRef.current = performance.now()
79
+ const batchSize = getBatchSize()
80
  let lastStep: ConvolutionStep | null = null
81
+ let stepsProcessed = 0
82
 
83
  for (let i = 0; i < batchSize; i++) {
84
  const result = gen.next()
85
  if (result.done) {
86
  if (lastStep && lastStep.x === -1) {
87
  // Final step with output image
 
 
 
 
 
 
 
88
  const output = convolveImage(originalImageData, kernel, padding, normalize)
89
  completeConvolution(output)
90
  }
 
92
  }
93
  lastStep = result.value
94
  updateConvolutionStep(result.value)
95
+ stepsProcessed++
96
+
97
+ // Check if we've spent too much time in this frame (yield to main thread)
98
+ if (performance.now() - frameStartRef.current > 8) {
99
+ break
100
+ }
101
  }
102
 
103
  animationRef.current = requestAnimationFrame(processBatch)
 
119
  updateConvolutionProgress,
120
  completeConvolution,
121
  setConvolutionError,
122
+ getBatchSize,
123
  ])
124
 
125
  const pause = useCallback(() => {
src/lib/convolution/engine.ts CHANGED
@@ -133,6 +133,10 @@ export function* convolutionSteps(
133
 
134
  const outputData = new Float32Array(width * height * channels)
135
 
 
 
 
 
136
  for (let y = 0; y < height; y++) {
137
  for (let x = 0; x < width; x++) {
138
  for (let c = 0; c < channels; c++) {
@@ -166,6 +170,49 @@ export function* convolutionSteps(
166
  processedPixels++
167
  const progress = processedPixels / totalPixels
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  yield {
170
  x,
171
  y,
@@ -177,6 +224,8 @@ export function* convolutionSteps(
177
  outputValue: normalizedValue,
178
  normalizedValue,
179
  progress,
 
 
180
  }
181
 
182
  if (onProgress) onProgress(progress)
@@ -202,6 +251,8 @@ export function* convolutionSteps(
202
  outputValue: 0,
203
  normalizedValue: 0,
204
  progress: 1,
 
 
205
  }
206
  }
207
 
 
133
 
134
  const outputData = new Float32Array(width * height * channels)
135
 
136
+ // For progressive rendering: yield partial output every N rows
137
+ const rowsPerYield = Math.max(1, Math.floor(height / 20)) // ~20 yields per image
138
+ let rowsSinceYield = 0
139
+
140
  for (let y = 0; y < height; y++) {
141
  for (let x = 0; x < width; x++) {
142
  for (let c = 0; c < channels; c++) {
 
170
  processedPixels++
171
  const progress = processedPixels / totalPixels
172
 
173
+ // Create partial output image for progressive rendering
174
+ let partialOutputImageData: ImageData | undefined
175
+ let isPartialOutput = false
176
+
177
+ if (c === channels - 1 && x === width - 1) {
178
+ // End of row - check if we should yield partial output
179
+ rowsSinceYield++
180
+ if (rowsSinceYield >= rowsPerYield || y === height - 1) {
181
+ // Create partial output image
182
+ const partialImageData = new ImageData(width, height)
183
+ for (let i = 0; i < outputData.length; i++) {
184
+ // Only fill up to current progress
185
+ const pixelIdx = Math.floor(i / channels)
186
+ const currentPixelIdx = y * width + x
187
+ if (pixelIdx <= currentPixelIdx) {
188
+ partialImageData.data[i] = Math.max(0, Math.min(255, Math.round(outputData[i] * 255)))
189
+ } else {
190
+ // Unprocessed pixels - show as transparent or original
191
+ partialImageData.data[i] = 0
192
+ partialImageData.data[i + 3] = 0 // transparent alpha for unprocessed
193
+ }
194
+ }
195
+ // Fill alpha for processed pixels
196
+ for (let py = 0; py <= y; py++) {
197
+ for (let px = 0; px < width; px++) {
198
+ for (let pc = 0; pc < channels; pc++) {
199
+ const idx = (py * width + px) * channels + pc
200
+ const imgIdx = (py * width + px) * 4 + pc
201
+ if (pc < 3) {
202
+ partialImageData.data[imgIdx] = Math.max(0, Math.min(255, Math.round(outputData[idx] * 255)))
203
+ } else {
204
+ partialImageData.data[imgIdx] = 255
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ partialOutputImageData = partialImageData
211
+ isPartialOutput = true
212
+ rowsSinceYield = 0
213
+ }
214
+ }
215
+
216
  yield {
217
  x,
218
  y,
 
224
  outputValue: normalizedValue,
225
  normalizedValue,
226
  progress,
227
+ partialOutputImageData,
228
+ isPartialOutput,
229
  }
230
 
231
  if (onProgress) onProgress(progress)
 
251
  outputValue: 0,
252
  normalizedValue: 0,
253
  progress: 1,
254
+ partialOutputImageData: outputImageData,
255
+ isPartialOutput: true,
256
  }
257
  }
258
 
src/store/index.ts CHANGED
@@ -265,12 +265,22 @@ export const useAppStore = create<AppStore>()(
265
  })),
266
 
267
  updateConvolutionStep: (step) =>
268
- set((state) => ({
269
- convolution: {
270
- ...state.convolution,
271
- currentStep: step,
272
- },
273
- })),
 
 
 
 
 
 
 
 
 
 
274
 
275
  updateConvolutionProgress: (progress, processedPixels, elapsedTime) =>
276
  set((state) => ({
 
265
  })),
266
 
267
  updateConvolutionStep: (step) =>
268
+ set((state) => {
269
+ const newState = {
270
+ convolution: {
271
+ ...state.convolution,
272
+ currentStep: step,
273
+ },
274
+ }
275
+ // If this step has a partial output image, update the processed image data
276
+ if (step.isPartialOutput && step.partialOutputImageData) {
277
+ return {
278
+ ...newState,
279
+ processedImageData: step.partialOutputImageData,
280
+ }
281
+ }
282
+ return newState
283
+ }),
284
 
285
  updateConvolutionProgress: (progress, processedPixels, elapsedTime) =>
286
  set((state) => ({
src/types/index.ts CHANGED
@@ -32,6 +32,9 @@ export interface ConvolutionStep {
32
  outputValue: number
33
  normalizedValue: number
34
  progress: number
 
 
 
35
  }
36
 
37
  export interface ConvolutionProgress {
 
32
  outputValue: number
33
  normalizedValue: number
34
  progress: number
35
+ // Progressive output (optional, set periodically during animation)
36
+ partialOutputImageData?: ImageData
37
+ isPartialOutput?: boolean
38
  }
39
 
40
  export interface ConvolutionProgress {