| Question | Decision |
|---|---|
| Build tooling | Vite |
| State management | React Context + hooks (add library only if needed) |
| Rendering | Three.js for main canvas (pixels as texture on plane) |
| Pixel inspection | Bottom bar (auto-tracks current pixel, click to pin) |
| Sample images | CDN-hosted at kernel-scope.ammaralam.me |
| Default state | Pre-loaded with first sample image |
| Sobel magnitude | sqrt(Gx² + Gy²) (accurate) |
| Worker granularity | Step-by-step capable (supports playback/slo-mo) |
| Timeline scrubber | Linear (every pixel position) |
| "Instant" mode | Process all remaining pixels immediately |
- Initialize Vite + React + TypeScript project
- Configure ESLint and Prettier
- Set up folder structure:
src/ components/ # React components hooks/ # Custom hooks workers/ # Web Worker files core/ # Pure algorithms (convolution, kernels) types/ # TypeScript interfaces constants/ # Kernel presets, config values utils/ # Helper functions - Install dependencies:
three(Three.js)@react-three/fiber(React renderer for Three.js)@react-three/drei(useful Three.js helpers)
- Create basic App shell with placeholder layout
- Define
Kerneltype (3×3 number matrix + optional divisor + name) - Implement
applyKernelToPixel(image, x, y, kernel, edgeMode)→ single output value - Implement edge handling modes:
- Zero padding: out-of-bounds → 0
- Mirror: reflect at boundary
- Clamp: repeat edge pixel
- Unit tests for
applyKernelToPixelwith all edge modes
- Define Sobel Gx kernel:
[[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]] - Define Sobel Gy kernel:
[[1, 2, 1], [0, 0, 0], [-1, -2, -1]] - Implement Sobel magnitude:
sqrt(Gx² + Gy²)(clamp to 0-255) - Define Gaussian blur kernel:
[[1, 2, 1], [2, 4, 2], [1, 2, 1]]with divisor 16 - Define Sharpen kernel:
[[0, -1, 0], [-1, 5, -1], [0, -1, 0]] - Unit tests for each preset kernel
-
loadImage(url | File)→ returns ImageData (grayscale, clamped to 1024×1024) -
rgbToGrayscale(imageData)→ grayscale ImageData -
resizeIfNeeded(imageData, maxDim)→ resized ImageData -
imageDataToUint8Array(imageData)→ flat grayscale array -
uint8ArrayToImageData(array, width, height)→ ImageData - Unit tests for image utilities
- Create
convolution.worker.ts - Define message protocol:
// Main → Worker type WorkerCommand = | { type: 'INIT'; image: Uint8Array; width: number; height: number; kernel: Kernel; edgeMode: EdgeMode } | { type: 'STEP' } // Compute next pixel | { type: 'STEP_N'; n: number } // Compute next N pixels | { type: 'RUN_TO_END' } // Compute all remaining pixels | { type: 'SEEK'; position: number } // Jump to position (recompute from start) | { type: 'RESET' } // Go back to position 0 // Worker → Main type WorkerResponse = | { type: 'PROGRESS'; position: number; totalPixels: number; outputPixel: { x: number; y: number; value: number } } | { type: 'BATCH_COMPLETE'; position: number; pixels: Array<{ x: number; y: number; value: number }> } | { type: 'COMPLETE'; outputImage: Uint8Array } | { type: 'READY' }
- Implement worker state machine:
- Stores source image, output image (in progress), current position
- On
STEP: compute one pixel, sendPROGRESS - On
STEP_N: compute N pixels, sendBATCH_COMPLETE - On
RUN_TO_END: compute all remaining, sendCOMPLETE - On
SEEK: recompute from 0 to position (for scrubbing backward)
- Create
useConvolutionWorkerhook:- Manages worker lifecycle
- Exposes:
init(),step(),stepN(),runToEnd(),seek(),reset() - Exposes state:
position,totalPixels,isRunning,outputImage
- Create
ImageCanvascomponent using@react-three/fiber - Set up orthographic camera for 2D view
- Create plane geometry sized to image dimensions
- Apply grayscale image as
DataTexture(LUMINANCE format) - Configure texture for pixelated rendering (
NearestFilter, no mipmaps)
- Implement zoom via scroll wheel (continuous)
- Store zoom level in state
- Update camera zoom
- Zoom toward mouse cursor position
- Implement pan via click-drag
- Track drag state
- Update camera position
- Add zoom preset buttons (1×, 4×, 16×, 64×)
- Add pixel grid overlay when zoom > threshold (e.g., 8×)
- Thin lines between pixels
- Use
LineSegmentsor shader
- Maintain two textures:
originalTexture,outputTexture - Update
outputTextureas convolution progresses - Support switching between textures for display
- Render two planes: original (left), processed (right)
- Sync zoom/pan between both views
- Add visual separator between views
- Toggle button to switch to single view
- Single plane with custom shader:
- Input:
originalTexture,outputTexture,currentPosition(raster index) - For each pixel: if
pixelRasterIndex < currentPosition, sample fromoutputTexture; else sample fromoriginalTexture - Raster index =
y * width + x
- Input:
- Visual boundary at current processing row (horizontal line + highlight on current pixel)
- Toggle button to switch to side-by-side
- Render 3×3 border overlay at current kernel position
- Position updates with convolution progress
- Visible in both view modes
- Style: semi-transparent colored border
- Play/Pause button
- Play: start interval calling
step()orstepN()based on speed - Pause: clear interval
- Play: start interval calling
- Speed slider
- Range: 1 px/sec to 10000 px/sec (log scale)
- "Instant" option at max
- Step forward button: advance 1 pixel
- Step backward button: seek to position - 1
- Horizontal slider from 0 to totalPixels
- Drag to seek to any position
- Show current position / total (e.g., "524,288 / 1,048,576")
- Visual progress indicator
- Toggle checkbox for follow mode
- When enabled:
- Camera centers on current kernel position
- Zoom set to show ~50×50 pixel region (adjustable)
- Smooth camera transitions (lerp)
- Header bar with app name "KernelScope"
- Left sidebar (controls panel, ~280px width)
- Main canvas area (fills remaining space)
- Bottom bar for playback controls
- Dropdown/grid of sample images (thumbnails)
- "Upload" button → file picker
- Accept: image/*
- Validate size ≤ 1024×1024 (or auto-resize with warning)
- Show current image info: dimensions, filename
- Dropdown or button group:
- Sobel Gx
- Sobel Gy
- Sobel Magnitude
- Gaussian Blur
- Sharpen
- Custom
- When "Custom" selected, show editable 3×3 grid
- Display current kernel matrix (read-only for presets)
- 3×3 grid of number inputs
- Each cell accepts decimal values
- "Apply" button to use custom kernel
- Optional: "Reset" to clear values
- Radio buttons or dropdown:
- Zero Padding (default)
- Mirror
- Clamp
- Brief tooltip explaining each option
- Checkbox: "Color-coded feedback"
- Checkbox: "Follow mode"
- View mode toggle: "Side-by-side" | "Single"
- Persistent panel (below kernel display or collapsible section)
- Click any pixel on canvas → update panel
- Display:
- Clicked position (x, y)
- Original value at that position
- Output value (if computed)
- 3×3 neighborhood grid with source values
- 3×3 kernel grid with weights
- 3×3 grid showing element-wise products
- Sum calculation with formula
- Visual formatting: aligned grids, clear labels
- "No pixel selected" placeholder when empty
- Track previous and current value for each pixel
- When pixel is computed:
- If new > old: flash green
- If new < old: flash red
- If new == old: no flash
- Implementation options:
- Shader-based: pass delta values, animate in shader
- Overlay-based: temporary colored squares
- Flash duration: ~200ms fade out
- Toggle via checkbox in options
- "Download" button in sidebar
- Convert output texture to canvas
- Export as PNG via
canvas.toBlob() - Filename:
kernelscope-{kernel}-{timestamp}.png - Disabled until convolution complete (or allow partial export)
- Tooltips for each kernel option explaining what it does
- Brief description panel that updates with selected kernel
- Example text:
- Sobel Gx: "Detects vertical edges by computing horizontal gradient"
- Gaussian: "Smooths the image by averaging neighboring pixels with weighted distribution"
- Help button (?) in header → modal with overview
- Invalid image upload → show error toast
- Image too large → auto-resize with notification
- Worker crash → show error, allow retry
- Profile Three.js render loop, optimize if needed
- Ensure 60fps during zoom/pan
- Test with 1024×1024 images
- Throttle scrubber seek operations
- Sidebar collapses on narrow screens
- Canvas resizes to fit container
- Touch support for mobile (basic pan/zoom)
- Consistent color scheme
- Loading states during image load/processing
- Smooth transitions for UI state changes
- Cursor changes (grab/grabbing for pan, pointer for buttons)
- Unit tests for all core algorithms
- Unit tests for image utilities
- Integration tests for worker communication
- Component tests for key interactions:
- Image upload flow
- Kernel selection
- Playback controls
- Pixel inspection
- Manual testing checklist:
- All preset kernels produce correct output
- Custom kernel works with various values
- All edge modes work correctly
- Zoom/pan smooth at all levels
- Playback controls responsive
- Scrubber works for full range
- Export produces valid PNG
- Redesign bottom bar layout: calculation grids on the left, playback controls (scrubber, buttons, speed) on the right
- Remove pixel inspection panel from the sidebar
- Create compact
BottomBarcomponent that houses both calculation display and playback controls side-by-side - Ensure no vertical scrolling is needed — everything fits in a fixed-height bottom bar at standard desktop resolutions
- Calculation display shows three compact 3×3 grids inline: neighborhood, kernel weights, element-wise products
- Show summary line: pixel position
(x, y), original value → output value, sum formula
- Pixel inspection auto-updates to show the calculation for the pixel currently being processed (driven by convolution position, not clicks)
- When convolution advances (play, step, seek), the calculation display updates to the current pixel
- Click-to-pin: clicking a pixel on the canvas pins the inspection to that pixel; a "pin" indicator and "unpin" button are shown
- When pinned, inspection stays on the pinned pixel even as convolution continues
- Unpinning returns to auto-tracking the current convolution pixel
- Debug and fix the color-coded feedback system — flashing does not currently work despite being wired up
- Verify that the worker sends
previousValuealongsidevaluein PROGRESS and BATCH_COMPLETE responses - Verify that
pixelUpdatesRefis populated correctly inuseConvolutionWorkerand drained each frame by the renderer - Verify the feedback overlay/shader correctly reads deltas and applies green (increase) / red (decrease) coloring
- Ensure flash decay (~200-300ms fade out) is visible and smooth
- Test color feedback at various playback speeds (1 px/sec, 100 px/sec, instant)
- Ensure the "Color-coded feedback" checkbox properly toggles the feature on/off
CDN Base URL: https://kernel-scope.ammaralam.me
| Image | URL | Size | Purpose |
|---|---|---|---|
| House | https://kernel-scope.ammaralam.me/house.png |
512×512 | Architecture - clear horizontal/vertical edges for Sobel |
| Peppers | https://kernel-scope.ammaralam.me/peppers.png |
512×512 | Classic test image - varied textures and curves |
| Einstein | https://kernel-scope.ammaralam.me/einstein.png |
490×600 | Portrait - edge detection on facial features |
| Blobs | https://kernel-scope.ammaralam.me/blobs.png |
512×512 | Simple geometric shapes - beginner-friendly |
| Circuit | https://kernel-scope.ammaralam.me/circuit.png |
800×263 | Fine patterns - shows detail preservation |
Default image on load: House (clean edges, good for first-time demonstration)
{
"dependencies": {
"react": "^18.x",
"react-dom": "^18.x",
"three": "^0.160.x",
"@react-three/fiber": "^8.x",
"@react-three/drei": "^9.x"
},
"devDependencies": {
"typescript": "^5.x",
"vite": "^5.x",
"@vitejs/plugin-react": "^4.x",
"vitest": "^1.x",
"@testing-library/react": "^14.x",
"eslint": "^8.x",
"prettier": "^3.x"
}
}| Phase | Tasks |
|---|---|
| 1. Project Setup | 5 |
| 2. Core Algorithms | 14 |
| 3. Web Worker | 5 |
| 4. Three.js Canvas | 10 |
| 5. View Modes | 8 |
| 6. Playback Controls | 9 |
| 7. UI Layout | 12 |
| 8. Pixel Inspection | 4 |
| 9. Color Feedback | 4 |
| 10. Export | 3 |
| 11. Educational | 4 |
| 12. Polish | 10 |
| 13. Testing | 8 |
| 14. Pixel Inspection Relocation | 8 |
| 15. Fix Color-Coded Feedback | 7 |
| Total | ~111 tasks |