Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Pixel Artanim 8bit

БесплатноНе проверен

PIXELANIM8 8-битный генератор GIF

GitHubEmbed

Описание

PIXELANIM8 8-битный генератор GIF

README

8-битный генератор GIF анимаций

Next.js TypeScript TailwindCSS Sharp


👤 Автор 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

Цветовые палитры

// 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;
}

📊 Производительность

Оптимизации

  1. Buffer вместо TypedArrays — прямая работа с памятью
  2. Параллельная обработка — Sharp использует libvips
  3. Минимальные аллокации — переиспользование буферов

Метрики

Размер Кадров Время генерации
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

from github.com/vlad-Stujhev1/pixel-artanim-8bit

Установка Pixel Artanim 8bit

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/vlad-Stujhev1/pixel-artanim-8bit

FAQ

Pixel Artanim 8bit MCP бесплатный?

Да, Pixel Artanim 8bit MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Pixel Artanim 8bit?

Нет, Pixel Artanim 8bit работает без API-ключей и переменных окружения.

Pixel Artanim 8bit — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Pixel Artanim 8bit в Claude Desktop, Claude Code или Cursor?

Открой Pixel Artanim 8bit на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Pixel Artanim 8bit with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории ai