Big optimizations for Remi Georges

This commit is contained in:
2025-09-30 23:45:19 +02:00
parent 64bdf3189c
commit b73678124a
4 changed files with 60 additions and 114 deletions

View File

@ -37,21 +37,21 @@ function App() {
} else if (settings.selectedGenerator === 'art-institute') {
newImages = await generateArtInstituteImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'waveform') {
newImages = generateWaveformImages(settings.gridSize, 2048)
newImages = generateWaveformImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'partials') {
newImages = generatePartialsImages(settings.gridSize, 2048)
newImages = generatePartialsImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'slides') {
newImages = generateSlidesImages(settings.gridSize, 2048)
newImages = generateSlidesImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'shapes') {
newImages = generateShapesImages(settings.gridSize, 2048)
newImages = generateShapesImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'bands') {
newImages = generateBandsImages(settings.gridSize, 2048)
newImages = generateBandsImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'dust') {
newImages = generateDustImages(settings.gridSize, 2048)
newImages = generateDustImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'geopattern') {
newImages = await generateGeopatternImages(settings.gridSize, 512)
} else if (settings.selectedGenerator === 'harmonics') {
newImages = generateHarmonicsImages(settings.gridSize, 2048)
newImages = generateHarmonicsImages(settings.gridSize, 512)
} else {
newImages = []
}
@ -69,7 +69,7 @@ function App() {
<div className="flex-1 flex flex-col min-h-0">
<GeneratorSelector onGenerate={handleGenerate} isGenerating={generating} />
{settings.selectedGenerator === 'from-photo' ? (
<PhotoGrid size={2048} />
<PhotoGrid size={512} />
) : settings.selectedGenerator === 'webcam' ? (
<WebcamGrid size={512} />
) : (

View File

@ -17,6 +17,7 @@ export default function AudioPanel() {
const [isPlaying, setIsPlaying] = useState(false)
const [volume, setVolume] = useState(0.7)
const audioPlayerRef = useRef<AudioPlayer | null>(null)
const drawnImageIdRef = useRef<string | null>(null)
const updateParam = <K extends keyof typeof params>(key: K, value: (typeof params)[K]) => {
@ -133,7 +134,7 @@ export default function AudioPanel() {
<div className="w-24 h-24 border border-gray-600">
<canvas
ref={canvas => {
if (canvas && selected.canvas) {
if (canvas && selected.canvas && drawnImageIdRef.current !== selected.id) {
const ctx = canvas.getContext('2d')!
canvas.width = selected.canvas.width
canvas.height = selected.canvas.height
@ -147,6 +148,7 @@ export default function AudioPanel() {
}
ctx.drawImage(selected.canvas, 0, 0)
drawnImageIdRef.current = selected.id
}
}}
className="w-full h-full block"

View File

@ -1,45 +1,59 @@
import { useStore } from '@nanostores/react'
import { useState } from 'react'
import { useRef, memo } from 'react'
import { generatedImages, selectedImage, isGenerating } from '../stores'
import type { GeneratedImage } from '../stores'
const ImageGridItem = memo(({ image, isSelected, onClick }: {
image: GeneratedImage
isSelected: boolean
onClick: () => void
}) => {
const drawnRef = useRef(false)
return (
<button
onClick={onClick}
className={`aspect-square border-2 transition-colors ${
isSelected ? 'border-white' : 'border-gray-700 hover:border-gray-500'
}`}
>
<canvas
ref={canvas => {
if (canvas && image.canvas && !drawnRef.current) {
const ctx = canvas.getContext('2d')!
canvas.width = image.canvas.width
canvas.height = image.canvas.height
canvas.style.width = '100%'
canvas.style.height = '100%'
if (image.generator === 'tixy') {
ctx.imageSmoothingEnabled = false
} else {
ctx.imageSmoothingEnabled = true
}
ctx.drawImage(image.canvas, 0, 0)
drawnRef.current = true
}
}}
className="w-full h-full block"
style={{ imageRendering: image.generator === 'tixy' ? 'pixelated' : 'auto' }}
/>
</button>
)
})
ImageGridItem.displayName = 'ImageGridItem'
export default function ImageGrid() {
const images = useStore(generatedImages)
const selected = useStore(selectedImage)
const generating = useStore(isGenerating)
const [hoveredImage, setHoveredImage] = useState<GeneratedImage | null>(null)
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
const [localMousePosition, setLocalMousePosition] = useState({ x: 0, y: 0 })
const handleImageClick = (image: GeneratedImage) => {
selectedImage.set(image)
}
const handleMouseEnter = (image: GeneratedImage, event: React.MouseEvent) => {
setHoveredImage(image)
setMousePosition({ x: event.clientX, y: event.clientY })
const rect = event.currentTarget.getBoundingClientRect()
setLocalMousePosition({
x: (event.clientX - rect.left) / rect.width,
y: (event.clientY - rect.top) / rect.height,
})
}
const handleMouseMove = (event: React.MouseEvent) => {
setMousePosition({ x: event.clientX, y: event.clientY })
if (hoveredImage) {
const rect = event.currentTarget.getBoundingClientRect()
setLocalMousePosition({
x: (event.clientX - rect.left) / rect.width,
y: (event.clientY - rect.top) / rect.height,
})
}
}
const handleMouseLeave = () => {
setHoveredImage(null)
}
if (generating) {
return (
<div className="flex-1 flex items-center justify-center">
@ -71,87 +85,17 @@ export default function ImageGrid() {
return (
<div className="flex-1 overflow-hidden relative">
<div className="h-full overflow-y-auto p-6">
<div className="grid grid-cols-5 gap-3">
<div className="grid grid-cols-4 gap-3">
{images.map(image => (
<button
<ImageGridItem
key={image.id}
image={image}
isSelected={selected?.id === image.id}
onClick={() => handleImageClick(image)}
onMouseEnter={e => handleMouseEnter(image, e)}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
className={`aspect-square border-2 transition-colors ${
selected?.id === image.id ? 'border-white' : 'border-gray-700 hover:border-gray-500'
}`}
>
<canvas
ref={canvas => {
if (canvas && image.canvas) {
const ctx = canvas.getContext('2d')!
canvas.width = image.canvas.width
canvas.height = image.canvas.height
canvas.style.width = '100%'
canvas.style.height = '100%'
if (image.generator === 'tixy') {
ctx.imageSmoothingEnabled = false
} else {
ctx.imageSmoothingEnabled = true
}
ctx.drawImage(image.canvas, 0, 0)
}
}}
className="w-full h-full block"
style={{ imageRendering: image.generator === 'tixy' ? 'pixelated' : 'auto' }}
/>
</button>
/>
))}
</div>
</div>
{hoveredImage && (
<div
className="fixed z-50 pointer-events-none border-2 border-white bg-black shadow-lg"
style={{
left: mousePosition.x + 20,
top: mousePosition.y - 100,
width: '200px',
height: '200px',
}}
>
<canvas
ref={canvas => {
if (canvas && hoveredImage.canvas) {
const ctx = canvas.getContext('2d')!
canvas.width = 200
canvas.height = 200
const zoomFactor = 1.5
const sourceCanvas = hoveredImage.canvas
const sourceWidth = sourceCanvas.width
const sourceHeight = sourceCanvas.height
const centerX = localMousePosition.x * sourceWidth
const centerY = localMousePosition.y * sourceHeight
const cropSize = 200 / zoomFactor
const cropX = Math.max(0, Math.min(sourceWidth - cropSize, centerX - cropSize / 2))
const cropY = Math.max(0, Math.min(sourceHeight - cropSize, centerY - cropSize / 2))
if (hoveredImage.generator === 'tixy') {
ctx.imageSmoothingEnabled = false
} else {
ctx.imageSmoothingEnabled = true
}
ctx.drawImage(sourceCanvas, cropX, cropY, cropSize, cropSize, 0, 0, 200, 200)
}
}}
className="w-full h-full block"
style={{ imageRendering: hoveredImage.generator === 'tixy' ? 'pixelated' : 'auto' }}
/>
</div>
)}
</div>
)
}

View File

@ -33,7 +33,7 @@ export interface AppSettings {
export const appSettings = atom<AppSettings>({
selectedGenerator: 'tixy',
gridSize: 25,
gridSize: 16,
backgroundColor: '#000000',
foregroundColor: '#ffffff',
})