kernel-convolution-visualizer / src /hooks /useStaticConvolution.ts
mihirmithani
Phase 2
25e7739
Raw History Blame
3.38 kB
import { useCallback, useMemo } from 'react'
import { useAppStore } from '@/store'
import { computeConvolutionStep, getPixel } from '@/lib/convolution/engine'
import type { ImageDataExtended, ConvolutionStep, PaddingMode } from '@/types'
export function useStaticConvolution() {
const {
originalImageData,
selectedKernel,
customKernelMatrix,
customKernelNormalize,
settings,
} = useAppStore()
const kernel = selectedKernel?.matrix ?? customKernelMatrix
const normalize = selectedKernel?.normalize ?? customKernelNormalize
const padding = settings.padding
// Create extended image data for the engine
const extendedImage = useMemo((): ImageDataExtended | null => {
if (!originalImageData) return null
return {
data: originalImageData.data,
width: originalImageData.width,
height: originalImageData.height,
channels: 4,
}
}, [originalImageData])
const computeStep = useCallback(
(x: number, y: number, channel: number = 0): ConvolutionStep | null => {
if (!extendedImage || !kernel) return null
// Clamp coordinates to valid range
const clampedX = Math.max(0, Math.min(x, extendedImage.width - 1))
const clampedY = Math.max(0, Math.min(y, extendedImage.height - 1))
const clampedChannel = Math.max(0, Math.min(channel, extendedImage.channels - 1))
return computeConvolutionStep(extendedImage, kernel, padding, normalize, clampedX, clampedY, clampedChannel)
},
[extendedImage, kernel, padding, normalize]
)
const getPixelValue = useCallback(
(x: number, y: number, channel: number = 0): number => {
if (!extendedImage) return 0
const clampedX = Math.max(0, Math.min(x, extendedImage.width - 1))
const clampedY = Math.max(0, Math.min(y, extendedImage.height - 1))
const clampedChannel = Math.max(0, Math.min(channel, extendedImage.channels - 1))
return getPixel(extendedImage, clampedX, clampedY, clampedChannel, padding)
},
[extendedImage, padding]
)
const getPaddedImagePreview = useCallback(
(kernelSize: number): { width: number; height: number; data: number[] } | null => {
if (!extendedImage) return null
const halfKernel = Math.floor(kernelSize / 2)
const paddedWidth = extendedImage.width + 2 * halfKernel
const paddedHeight = extendedImage.height + 2 * halfKernel
const data: number[] = []
for (let py = 0; py < paddedHeight; py++) {
for (let px = 0; px < paddedWidth; px++) {
const origX = px - halfKernel
const origY = py - halfKernel
// Sample each channel
for (let c = 0; c < Math.min(3, extendedImage.channels); c++) {
data.push(getPixel(extendedImage, origX, origY, c, padding))
}
// Alpha channel
if (extendedImage.channels > 3) {
data.push(getPixel(extendedImage, origX, origY, 3, padding))
}
}
}
return { width: paddedWidth, height: paddedHeight, data }
},
[extendedImage, padding]
)
return {
computeStep,
getPixelValue,
getPaddedImagePreview,
kernel,
normalize,
padding,
imageWidth: extendedImage?.width ?? 0,
imageHeight: extendedImage?.height ?? 0,
imageChannels: extendedImage?.channels ?? 0,
hasImage: !!extendedImage,
hasKernel: !!kernel,
}
}