File size: 3,376 Bytes
25e7739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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,
  }
}