import { create } from 'zustand'; import type { Selection, SelectionType, SelectionMode, SelectionState, } from '@/types/selection'; interface SelectionStore extends SelectionState { setActiveSelection: (selection: Selection | null) => void; setSelectionType: (type: SelectionType) => void; setSelectionMode: (mode: SelectionMode) => void; setFeather: (feather: number) => void; setTolerance: (tolerance: number) => void; setMarching: (isMarching: boolean) => void; clearSelection: () => void; selectAll: () => void; invertSelection: () => void; expandSelection: (pixels: number) => void; contractSelection: (pixels: number) => void; featherSelection: (radius: number) => void; } export const useSelectionStore = create((set) => ({ activeSelection: null, selectionType: 'rectangular', selectionMode: 'new', feather: 0, tolerance: 32, isMarching: true, setActiveSelection: (selection) => set({ activeSelection: selection, }), setSelectionType: (type) => set({ selectionType: type, }), setSelectionMode: (mode) => set({ selectionMode: mode, }), setFeather: (feather) => set({ feather: Math.max(0, Math.min(250, feather)), }), setTolerance: (tolerance) => set({ tolerance: Math.max(0, Math.min(255, tolerance)), }), setMarching: (isMarching) => set({ isMarching, }), clearSelection: () => set({ activeSelection: null, }), selectAll: () => set(() => { const { useCanvasStore, useLayerStore } = require('@/store'); const { width, height } = useCanvasStore.getState(); const { getActiveLayer } = useLayerStore.getState(); const activeLayer = getActiveLayer(); if (!activeLayer) return {}; // Create a mask that covers the entire canvas const maskData = new Uint8Array(width * height).fill(255); return { activeSelection: { id: `selection-${Date.now()}`, layerId: activeLayer.id, mask: { width, height, data: maskData, bounds: { x: 0, y: 0, width, height, }, }, inverted: false, feather: 0, createdAt: Date.now(), }, }; }), invertSelection: () => set((state) => { if (state.activeSelection) { return { activeSelection: { ...state.activeSelection, inverted: !state.activeSelection.inverted, }, }; } return state; }), expandSelection: (pixels) => set((state) => { if (!state.activeSelection) return state; const { mask } = state.activeSelection; const { width, height, data } = mask; const newData = new Uint8Array(data.length); // Expand selection by dilating the mask for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const idx = y * width + x; let maxValue = data[idx]; // Check neighbors within radius for (let dy = -pixels; dy <= pixels; dy++) { for (let dx = -pixels; dx <= pixels; dx++) { const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { const nidx = ny * width + nx; if (data[nidx] > maxValue) { maxValue = data[nidx]; } } } } newData[idx] = maxValue; } } return { activeSelection: { ...state.activeSelection, mask: { ...mask, data: newData, }, }, }; }), contractSelection: (pixels) => set((state) => { if (!state.activeSelection) return state; const { mask } = state.activeSelection; const { width, height, data } = mask; const newData = new Uint8Array(data.length); // Contract selection by eroding the mask for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const idx = y * width + x; let minValue = data[idx]; // Check neighbors within radius for (let dy = -pixels; dy <= pixels; dy++) { for (let dx = -pixels; dx <= pixels; dx++) { const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { const nidx = ny * width + nx; if (data[nidx] < minValue) { minValue = data[nidx]; } } } } newData[idx] = minValue; } } return { activeSelection: { ...state.activeSelection, mask: { ...mask, data: newData, }, }, }; }), featherSelection: (radius) => set((state) => { if (!state.activeSelection) return state; const { mask } = state.activeSelection; const { width, height, data } = mask; const newData = new Uint8Array(data.length); // Apply Gaussian blur for feathering const sigma = radius / 3; const kernelSize = Math.ceil(radius * 2) + 1; const kernel: number[] = []; let kernelSum = 0; // Generate Gaussian kernel for (let i = 0; i < kernelSize; i++) { const x = i - Math.floor(kernelSize / 2); const value = Math.exp(-(x * x) / (2 * sigma * sigma)); kernel.push(value); kernelSum += value; } // Normalize kernel for (let i = 0; i < kernel.length; i++) { kernel[i] /= kernelSum; } // Horizontal pass const temp = new Uint8Array(data.length); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let sum = 0; const halfKernel = Math.floor(kernelSize / 2); for (let k = 0; k < kernelSize; k++) { const sx = x + k - halfKernel; if (sx >= 0 && sx < width) { sum += data[y * width + sx] * kernel[k]; } } temp[y * width + x] = Math.round(sum); } } // Vertical pass for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let sum = 0; const halfKernel = Math.floor(kernelSize / 2); for (let k = 0; k < kernelSize; k++) { const sy = y + k - halfKernel; if (sy >= 0 && sy < height) { sum += temp[sy * width + x] * kernel[k]; } } newData[y * width + x] = Math.round(sum); } } return { activeSelection: { ...state.activeSelection, mask: { ...mask, data: newData, }, feather: radius, }, }; }), }));