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') {
|
} else if (settings.selectedGenerator === 'art-institute') {
|
||||||
newImages = await generateArtInstituteImages(settings.gridSize, 512)
|
newImages = await generateArtInstituteImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'waveform') {
|
} else if (settings.selectedGenerator === 'waveform') {
|
||||||
newImages = generateWaveformImages(settings.gridSize, 2048)
|
newImages = generateWaveformImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'partials') {
|
} else if (settings.selectedGenerator === 'partials') {
|
||||||
newImages = generatePartialsImages(settings.gridSize, 2048)
|
newImages = generatePartialsImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'slides') {
|
} else if (settings.selectedGenerator === 'slides') {
|
||||||
newImages = generateSlidesImages(settings.gridSize, 2048)
|
newImages = generateSlidesImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'shapes') {
|
} else if (settings.selectedGenerator === 'shapes') {
|
||||||
newImages = generateShapesImages(settings.gridSize, 2048)
|
newImages = generateShapesImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'bands') {
|
} else if (settings.selectedGenerator === 'bands') {
|
||||||
newImages = generateBandsImages(settings.gridSize, 2048)
|
newImages = generateBandsImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'dust') {
|
} else if (settings.selectedGenerator === 'dust') {
|
||||||
newImages = generateDustImages(settings.gridSize, 2048)
|
newImages = generateDustImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'geopattern') {
|
} else if (settings.selectedGenerator === 'geopattern') {
|
||||||
newImages = await generateGeopatternImages(settings.gridSize, 512)
|
newImages = await generateGeopatternImages(settings.gridSize, 512)
|
||||||
} else if (settings.selectedGenerator === 'harmonics') {
|
} else if (settings.selectedGenerator === 'harmonics') {
|
||||||
newImages = generateHarmonicsImages(settings.gridSize, 2048)
|
newImages = generateHarmonicsImages(settings.gridSize, 512)
|
||||||
} else {
|
} else {
|
||||||
newImages = []
|
newImages = []
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ function App() {
|
|||||||
<div className="flex-1 flex flex-col min-h-0">
|
<div className="flex-1 flex flex-col min-h-0">
|
||||||
<GeneratorSelector onGenerate={handleGenerate} isGenerating={generating} />
|
<GeneratorSelector onGenerate={handleGenerate} isGenerating={generating} />
|
||||||
{settings.selectedGenerator === 'from-photo' ? (
|
{settings.selectedGenerator === 'from-photo' ? (
|
||||||
<PhotoGrid size={2048} />
|
<PhotoGrid size={512} />
|
||||||
) : settings.selectedGenerator === 'webcam' ? (
|
) : settings.selectedGenerator === 'webcam' ? (
|
||||||
<WebcamGrid size={512} />
|
<WebcamGrid size={512} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export default function AudioPanel() {
|
|||||||
const [isPlaying, setIsPlaying] = useState(false)
|
const [isPlaying, setIsPlaying] = useState(false)
|
||||||
const [volume, setVolume] = useState(0.7)
|
const [volume, setVolume] = useState(0.7)
|
||||||
const audioPlayerRef = useRef<AudioPlayer | null>(null)
|
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]) => {
|
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">
|
<div className="w-24 h-24 border border-gray-600">
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvas => {
|
ref={canvas => {
|
||||||
if (canvas && selected.canvas) {
|
if (canvas && selected.canvas && drawnImageIdRef.current !== selected.id) {
|
||||||
const ctx = canvas.getContext('2d')!
|
const ctx = canvas.getContext('2d')!
|
||||||
canvas.width = selected.canvas.width
|
canvas.width = selected.canvas.width
|
||||||
canvas.height = selected.canvas.height
|
canvas.height = selected.canvas.height
|
||||||
@ -147,6 +148,7 @@ export default function AudioPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.drawImage(selected.canvas, 0, 0)
|
ctx.drawImage(selected.canvas, 0, 0)
|
||||||
|
drawnImageIdRef.current = selected.id
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="w-full h-full block"
|
className="w-full h-full block"
|
||||||
|
|||||||
@ -1,45 +1,59 @@
|
|||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import { useState } from 'react'
|
import { useRef, memo } from 'react'
|
||||||
import { generatedImages, selectedImage, isGenerating } from '../stores'
|
import { generatedImages, selectedImage, isGenerating } from '../stores'
|
||||||
import type { GeneratedImage } 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() {
|
export default function ImageGrid() {
|
||||||
const images = useStore(generatedImages)
|
const images = useStore(generatedImages)
|
||||||
const selected = useStore(selectedImage)
|
const selected = useStore(selectedImage)
|
||||||
const generating = useStore(isGenerating)
|
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) => {
|
const handleImageClick = (image: GeneratedImage) => {
|
||||||
selectedImage.set(image)
|
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) {
|
if (generating) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex items-center justify-center">
|
<div className="flex-1 flex items-center justify-center">
|
||||||
@ -71,87 +85,17 @@ export default function ImageGrid() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex-1 overflow-hidden relative">
|
<div className="flex-1 overflow-hidden relative">
|
||||||
<div className="h-full overflow-y-auto p-6">
|
<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 => (
|
{images.map(image => (
|
||||||
<button
|
<ImageGridItem
|
||||||
key={image.id}
|
key={image.id}
|
||||||
|
image={image}
|
||||||
|
isSelected={selected?.id === image.id}
|
||||||
onClick={() => handleImageClick(image)}
|
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>
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export interface AppSettings {
|
|||||||
|
|
||||||
export const appSettings = atom<AppSettings>({
|
export const appSettings = atom<AppSettings>({
|
||||||
selectedGenerator: 'tixy',
|
selectedGenerator: 'tixy',
|
||||||
gridSize: 25,
|
gridSize: 16,
|
||||||
backgroundColor: '#000000',
|
backgroundColor: '#000000',
|
||||||
foregroundColor: '#ffffff',
|
foregroundColor: '#ffffff',
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user