Big optimizations for Remi Georges
This commit is contained in:
16
src/App.tsx
16
src/App.tsx
@ -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} />
|
||||
) : (
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ export interface AppSettings {
|
||||
|
||||
export const appSettings = atom<AppSettings>({
|
||||
selectedGenerator: 'tixy',
|
||||
gridSize: 25,
|
||||
gridSize: 16,
|
||||
backgroundColor: '#000000',
|
||||
foregroundColor: '#ffffff',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user