githubinferredactive
pixel-artanim-8bit
provenance:github:vlad-Stujhev1/pixel-artanim-8bit
PIXELANIM8 8-битный генератор GIF
README
# PIXELANIM8 🎮
## 8-битный генератор GIF анимаций
<p align="center">
<img src="https://img.shields.io/badge/Next.js-16-black?style=flat-square&logo=next.js" alt="Next.js">
<img src="https://img.shields.io/badge/TypeScript-5-blue?style=flat-square&logo=typescript" alt="TypeScript">
<img src="https://img.shields.io/badge/TailwindCSS-4-cyan?style=flat-square&logo=tailwindcss" alt="TailwindCSS">
<img src="https://img.shields.io/badge/Sharp-Image%20Processing-green?style=flat-square" alt="Sharp">
</p>
---
## 👤 Автор vs_lab
AI Engineer | FoodTech & Marketplaces | LLM, MLOps
Проект создан с использованием AI-ассистента, который прошёл путь самопознания в нетривиальной задаче pixel-art генерации.
🧠 О проекте: AI-перспектива
Когда мне поставили задачу создать 8-битный генератор GIF, я не просто скопировал готовые решения. Я прошёл через несколько этапов "самопознания":
1. **Архитектурные решения** — почему Sharp, а не Canvas? Почему gif-encoder-2, а не FFmpeg?
2. **Математика анимаций** — каждый эффект требует уникальных алгоритмов
3. **Квантование цветов** — как превратить 16 миллионов цветов в 8-битную палитру?
4. **Производительность** — обработка пикселей в Node.js без блокировки event loop
Это нетривиальная задача, где каждый пиксель имеет значение.
---
## 🎯 Что умеет PixelAnim8
### Входные данные
- **Drag & Drop** загрузка изображений
- Поддержка форматов: PNG, JPEG, GIF, BMP
- Максимальный размер: 5MB
### Анимации (математическое описание)
| Эффект | Алгоритм | Математика |
|--------|----------|------------|
| **Pulse** | Изменение яркости | `brightness = 0.7 + sin(progress * 2π) * 0.3` |
| **Blink** | Периодическое затемнение | Пороговый фильтр по фреймам |
| **Shake** | Случайное смещение | `offset = round((random - 0.5) * 4)` |
| **Spin** | Вращение вокруг центра | Матрица поворота: `cos(θ), -sin(θ)` |
| **Bounce** | Прыжок по вертикали | `offset = floor(abs(sin(progress * 2π)) * 8)` |
| **Wave** | Волновое искажение | `wave = sin(y/height * 4π + progress * 2π) * 4` |
| **Zoom** | Масштабирование | `scale = 0.8 + sin(progress * 2π) * 0.2` |
| **Rainbow** | Цветовой сдвиг | HSL rotate: `hue = (h + progress * 1080) % 360` |
### Цветовые палитры
```typescript
// NES (Nintendo Entertainment System) - 8 цветов
nes: [
[15, 15, 15], // Черный
[29, 43, 83], // Темно-синий
[126, 37, 83], // Темно-фиолетовый
[0, 135, 81], // Темно-зеленый
[171, 82, 54], // Коричневый
[95, 87, 79], // Темно-серый
[194, 195, 199], // Светло-серый
[255, 241, 232] // Белый
]
// Game Boy - 4 оттенка зелёного
gameboy: [[15, 56, 15], [48, 98, 48], [139, 172, 15], [155, 188, 15]]
// Commodore 64 - 8 характерных цветов
c64: [[0,0,0], [255,255,255], [136,0,0], [170,255,238], ...]
// Monochrome - классическая черно-белая
mono: [[0, 0, 0], [255, 255, 255]]
```
---
## 🏗️ Архитектура
```
src/
├── app/
│ ├── page.tsx # Главная страница (Zustand state)
│ ├── layout.tsx # Root layout с Google Fonts
│ ├── globals.css # Retro CSS переменные
│ └── api/
│ └── generate/
│ └── route.ts # API endpoint для генерации GIF
├── lib/
│ ├── types.ts # TypeScript интерфейсы
│ └── store.ts # Zustand store с localStorage persist
└── components/
└── pixelanim8/
├── ImageUpload.tsx # Drag & drop загрузка
├── PromptInput.tsx # Ввод промпта
├── ParametersPanel.tsx # Настройки палитры/размера
├── GenerateButton.tsx # Кнопка генерации
├── ResultDisplay.tsx # CRT-монитор с результатом
└── HistoryGallery.tsx # История генераций
```
### Ключевые технологии
| Технология | Назначение | Почему выбрана |
|------------|------------|----------------|
| **Sharp** | Обработка изображений | Быстрый нативный модуль, поддерживает resize, raw pixels |
| **gif-encoder-2** | Создание GIF | Оптимизированный энкодер с поддержкой quantization |
| **Zustand** | State management | Минимальный boilerplate, встроенный persist |
| **TailwindCSS 4** | Стилизация | CSS переменные для ретро-темы |
---
## 🧮 Математика квантования цветов
```typescript
// Евклидово расстояние в RGB пространстве
function findClosestColor(r: number, g: number, b: number, palette: number[][]): number[] {
let minDist = Infinity;
let closest = palette[0];
for (const color of palette) {
const dist = Math.sqrt(
(r - color[0]) ** 2 +
(g - color[1]) ** 2 +
(b - color[2]) ** 2
);
if (dist < minDist) {
minDist = dist;
closest = color;
}
}
return closest;
}
```
### RGB ↔ HSL конверсия
Для эффекта Rainbow используется конвертация в HSL:
```typescript
// RGB → HSL
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h = 0, s = 0;
const l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return [h * 360, s, l];
}
// HSL → RGB (обратная конверсия)
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
// ... full implementation in route.ts
}
```
---
## 🚀 Как запускать
### Development
```bash
# Установка зависимостей
bun install
# Запуск dev сервера
bun run dev
# Линтинг
bun run lint
```
### Production Build
```bash
bun run build
bun run start
```
### Деплой на Netlify
Проект включает готовую конфигурацию для Netlify:
```
netlify-app/
├── netlify.toml # Конфигурация functions
├── netlify/functions/
│ └── generate.js # Serverless function
└── public/
└── index.html # Статический фронтенд
```
---
## 🔧 Как бустить новыми фишками
### 1. Добавление новых анимаций
```typescript
// В src/app/api/generate/route.ts
// 1. Добавь парсинг в parseAnimation()
function parseAnimation(prompt: string): string {
const p = prompt.toLowerCase();
// ... existing conditions
if (p.includes('glitch')) return 'glitch'; // ← Новая анимация
return 'default';
}
// 2. Реализуй эффект в createFrames()
case 'glitch': {
// Алгоритм глитч-эффекта
const glitched = Buffer.from(baseData);
const sliceHeight = Math.floor(height * 0.1);
const sliceY = Math.floor(Math.random() * (height - sliceHeight));
const offset = Math.floor((Math.random() - 0.5) * 20);
// Сдвиг горизонтальной полосы
for (let y = sliceY; y < sliceY + sliceHeight; y++) {
for (let x = 0; x < width; x++) {
const srcX = (x + offset + width) % width;
const srcIdx = (y * width + srcX) * 4;
const dstIdx = (y * width + x) * 4;
// ... copy pixels
}
}
frames.push(glitched);
continue;
}
```
### 2. Новые цветовые палитры
```typescript
// Добавь в объект PALETTES
const PALETTES = {
// ... existing palettes
sega: [
[0, 0, 0], [0, 0, 170], [0, 170, 0], [0, 170, 170],
[170, 0, 0], [170, 0, 170], [170, 85, 0], [170, 170, 170],
[85, 85, 85], [85, 85, 255], [85, 255, 85], [85, 255, 255],
[255, 85, 85], [255, 85, 255], [255, 255, 85], [255, 255, 255]
],
// Sega Master System - 16 цветов
};
```
### 3. Математические эффекты для добавления
```typescript
// === ПОТОКОПИКСЕЛЬНАЯ ОТРИСОВКА ===
// Пиксели появляются один за другим
case 'pixel-reveal': {
const totalPixels = width * height;
const visiblePixels = Math.floor(progress * totalPixels);
const revealed = Buffer.alloc(frame.length);
for (let i = 0; i < visiblePixels * 4; i++) {
revealed[i] = baseData[i];
}
frames.push(revealed);
continue;
}
// === ЦВЕТОВАЯ ВОЛНА ===
// Прорисовка по цветам (сортировка по hue)
case 'color-wave': {
const sortedPixels: Array<{x: number, y: number, hue: number}> = [];
// Сортировка пикселей по hue
for (let y = 0; y < height; y++) {
for
[truncated…]PUBLIC HISTORY
First discoveredMar 21, 2026
IDENTITY
inferred
Identity inferred from code signals. No PROVENANCE.yml found.
Is this yours? Claim it →METADATA
platformgithub
first seenMar 10, 2026
last updatedMar 16, 2026
last crawled20 days ago
version—
README BADGE
Add to your README:
