About
PIXELANIM8 8-битный генератор GIF
README
8-битный генератор GIF анимаций
👤 Автор vs_lab
AI Engineer | FoodTech & Marketplaces | LLM, MLOps Проект создан с использованием AI-ассистента, который прошёл путь самопознания в нетривиальной задаче pixel-art генерации.
🧠 О проекте: AI-перспектива
Когда мне поставили задачу создать 8-битный генератор GIF, я не просто скопировал готовые решения. Я прошёл через несколько этапов "самопознания":
- Архитектурные решения — почему Sharp, а не Canvas? Почему gif-encoder-2, а не FFmpeg?
- Математика анимаций — каждый эффект требует уникальных алгоритмов
- Квантование цветов — как превратить 16 миллионов цветов в 8-битную палитру?
- Производительность — обработка пикселей в 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 |
Цветовые палитры
// 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 переменные для ретро-темы |
🧮 Математика квантования цветов
// Евклидово расстояние в 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:
// 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
# Установка зависимостей
bun install
# Запуск dev сервера
bun run dev
# Линтинг
bun run lint
Production Build
bun run build
bun run start
Деплой на Netlify
Проект включает готовую конфигурацию для Netlify:
netlify-app/
├── netlify.toml # Конфигурация functions
├── netlify/functions/
│ └── generate.js # Serverless function
└── public/
└── index.html # Статический фронтенд
🔧 Как бустить новыми фишками
1. Добавление новых анимаций
// В 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. Новые цветовые палитры
// Добавь в объект 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. Математические эффекты для добавления
// === ПОТОКОПИКСЕЛЬНАЯ ОТРИСОВКА ===
// Пиксели появляются один за другим
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 (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
const [h] = rgbToHsl(baseData[idx], baseData[idx+1], baseData[idx+2]);
sortedPixels.push({x, y, hue: h});
}
}
sortedPixels.sort((a, b) => a.hue - b.hue);
const waveProgress = Math.floor(progress * sortedPixels.length);
const wavedFrame = Buffer.alloc(frame.length);
for (let i = 0; i < waveProgress; i++) {
const {x, y} = sortedPixels[i];
const idx = (y * width + x) * 4;
wavedFrame[idx] = baseData[idx];
wavedFrame[idx + 1] = baseData[idx + 1];
wavedFrame[idx + 2] = baseData[idx + 2];
wavedFrame[idx + 3] = 255;
}
frames.push(wavedFrame);
continue;
}
// === ГРАНИЧНАЯ АНИМАЦИЯ (по соседним пикселям) ===
// Распространение от краёв к центру
case 'edge-scan': {
const distanceFromEdge = Math.min(progress * Math.max(width, height), Math.max(width, height));
const scanned = Buffer.alloc(frame.length);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const distToEdge = Math.min(x, y, width - x - 1, height - y - 1);
const srcIdx = (y * width + x) * 4;
if (distToEdge <= distanceFromEdge) {
scanned[srcIdx] = baseData[srcIdx];
scanned[srcIdx + 1] = baseData[srcIdx + 1];
scanned[srcIdx + 2] = baseData[srcIdx + 2];
scanned[srcIdx + 3] = 255;
}
}
}
frames.push(scanned);
continue;
}
// === ФРАКТАЛЬНЫЙ ШУМ ===
case 'noise-field': {
const noiseScale = 0.05;
const timeOffset = progress * 10;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
const noise = Math.sin(x * noiseScale + timeOffset) *
Math.cos(y * noiseScale + timeOffset);
const intensity = 0.8 + noise * 0.2;
frame[idx] = Math.min(255, Math.floor(baseData[idx] * intensity));
frame[idx + 1] = Math.min(255, Math.floor(baseData[idx + 1] * intensity));
frame[idx + 2] = Math.min(255, Math.floor(baseData[idx + 2] * intensity));
}
}
break;
}
📊 Производительность
Оптимизации
- Buffer вместо TypedArrays — прямая работа с памятью
- Параллельная обработка — Sharp использует libvips
- Минимальные аллокации — переиспользование буферов
Метрики
| Размер | Кадров | Время генерации |
|---|---|---|
| 128px | 4 | ~200ms |
| 256px | 8 | ~500ms |
| 512px | 16 | ~1.5s |
🎨 UI/UX особенности
Retro-стилизация
/* Пиксельная сетка фона */
.pixel-grid-bg {
background-image:
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255, 204, 0, 0.03) 2px),
repeating-linear-gradient(90deg, transparent, transparent 2px, rgba(255, 204, 0, 0.03) 2px);
}
/* CRT-монитор */
.monitor-frame {
background: linear-gradient(145deg, #8b8b8b 0%, #6b6b6b 50%, #8b8b8b 100%);
box-shadow: inset 0 2px 4px rgba(255,255,255,0.3),
inset 0 -2px 4px rgba(0,0,0,0.3),
0 8px 32px rgba(0,0,0,0.5);
}
.monitor-screen {
box-shadow: inset 0 0 20px rgba(0, 255, 0, 0.1),
inset 0 0 60px rgba(0, 0, 0, 0.8);
}
Шрифты
- Press Start 2P — заголовки, кнопки (пиксельный шрифт)
- VT323 — основной текст (терминальный шрифт)
🔌 API Endpoints
POST /api/generate
// Request
{
image: string, // Base64 data URL
prompt: string, // Natural language description
params: {
palette: 'nes' | 'gameboy' | 'c64' | 'mono',
frames: number, // 1-16
delay: number, // FPS (1-30)
size: '128' | '256' | '512'
}
}
// Response
{
success: boolean,
gifData?: string, // Base64 GIF data URL
metadata?: {
frameCount: number,
palette: string,
fileSize: number,
dimensions: { width: number, height: number }
},
error?: string
}
📦 Зависимости
{
"dependencies": {
"sharp": "^0.33.x", // Image processing
"gif-encoder-2": "^1.0.x", // GIF generation
"zustand": "^4.5.x", // State management
"next": "^16.x", // Framework
"react": "^19.x", // UI
"tailwindcss": "^4.x" // Styling
}
}
🛠️ Roadmap улучшений архитектуры
1. WebWorker для обработки
// worker.ts
self.onmessage = (e) => {
const { baseData, width, height, animationType, frameCount } = e.data;
const frames = createFrames(baseData, width, height, animationType, frameCount);
self.postMessage({ frames });
};
2. Кэширование результатов
import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, Buffer>({
max: 100,
ttl: 1000 * 60 * 60, // 1 hour
});
// Cache key = hash(image + prompt + params)
3. Streaming response
// Для больших GIF
const stream = new ReadableStream({
async pull(controller) {
for (const frame of frames) {
controller.enqueue(frame);
}
controller.close();
}
});
4. WebSocket прогресс
// Реал-тайм обновления прогресса
io.on('connection', (socket) => {
socket.on('generate', async (data) => {
for (let i = 0; i < frameCount; i++) {
const frame = await createFrame(i);
socket.emit('progress', { frame: i, total: frameCount });
}
});
});
📝 Лицензия
MIT License - используй как хочешь, создавай ретро-арт!
🙏 Благодарности
- Nintendo — за эстетику NES
- Nintendo — за культовый Game Boy
- Commodore — за C64 и его легендарную палитру
- Sharp и gif-encoder-2 — за инструменты
Made with ❤️ for retro gaming fans
2024 PixelAnim8
🤖 AI Note
Этот проект — результат коллаборации человека и AI. Каждый пиксель, каждый алгоритм, каждая CSS-переменная были созданы через диалог, итерации и debug. Нетривиальная задача генерации pixel-art анимаций потребовала:
- Архитектурного мышления — выбор правильных инструментов
- Математической точности — формулы анимаций и квантования
- Эстетического чувства — ретро-стилизация UI
- Отладки — исправление OOM, CSS import order, gif-encoder API
Generated with 💛 by AI Engineer | FoodTech & Marketplaces | LLM, MLOps
Installing Pixel Artanim 8bit
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/vlad-Stujhev1/pixel-artanim-8bitFAQ
Is Pixel Artanim 8bit MCP free?
Yes, Pixel Artanim 8bit MCP is free — one-click install via Unyly at no cost.
Does Pixel Artanim 8bit need an API key?
No, Pixel Artanim 8bit runs without API keys or environment variables.
Is Pixel Artanim 8bit hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Pixel Artanim 8bit in Claude Desktop, Claude Code or Cursor?
Open Pixel Artanim 8bit on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Fetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
by modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
by xuzexin-hzMCP-Agent
A simple, composable framework to build agents using Model Context Protocol by [LastMile AI](https://www.lastmileai.dev)
by lastmile-aiSpring AI MCP Client
Provides auto-configuration for MCP client functionality in Spring Boot applications.
mcp.natoma.ai
A Hosted MCP Platform to discover, install, manage and deploy MCP servers by [Natoma Labs](https://www.natoma.ai)
MCPHub
Website to list high quality MCP servers and reviews by real users. Also provide online chatbot for popular LLM models with MCP server support.
MCP Servers Rating and User Reviews
Website to rate MCP servers, write authentic user reviews, and [search engine for agent & mcp](http://www.deepnlp.org/search/agent)
mkinf
An Open Source registry of hosted MCP Servers to accelerate AI agent workflows.
Compare Pixel Artanim 8bit with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All ai MCPs
