This guide covers setting up your development environment, coding standards, and best practices for contributing to Dither Magic.
- Python 3.11 or higher
- Node.js 18 or higher
- Git
- pip and npm
git clone https://github.com/YOUR_USERNAME/dither-magic.git
cd dither-magicPython Backend:
pip install pillow numpy werkzeug flask pytestFrontend:
npm installYou'll need two terminal windows:
Terminal 1 - Backend:
python app.pyThe Flask backend will start on http://localhost:5000
Terminal 2 - Frontend:
npm run devThe Vite dev server will start on http://localhost:5173
Navigate to http://localhost:5173 in your browser.
git checkout -b feature/your-feature-nameUse descriptive branch names:
feature/for new featuresfix/for bug fixesdocs/for documentation updatesrefactor/for code refactoring
- Make your changes following the code style guidelines
- Test your changes thoroughly
- Update documentation as needed
- Ensure all tests pass
Use clear, descriptive commit messages:
git commit -m "Add feature: brief description"Good commit messages:
- "Add blue noise dithering algorithm"
- "Fix memory leak in batch processing"
- "Update API documentation for new parameters"
Bad commit messages:
- "Update"
- "Fix bug"
- "Changes"
git push origin feature/your-feature-name- Go to GitHub and open a new pull request
- Provide a clear description of your changes
- Reference any related issues
- Add screenshots for UI changes
Style Guide: Follow PEP 8
Key Points:
- Maximum line length: 100 characters
- Use 4 spaces for indentation (no tabs)
- Use snake_case for functions and variables
- Use PascalCase for classes
- Add docstrings to all functions
Example:
def process_image(image: Image.Image, threshold: int = 127) -> Image.Image:
"""
Process an image with a threshold.
Args:
image: Input PIL Image object
threshold: Threshold value for processing (default: 127)
Returns:
Processed PIL Image object
Example:
>>> img = Image.open('photo.jpg')
>>> result = process_image(img, threshold=128)
"""
# Convert to grayscale
img = image.convert('L')
# Apply processing
arr = np.array(img)
arr = (arr > threshold) * 255
return Image.fromarray(arr.astype(np.uint8))Style Guide: ESLint with React plugin
Key Points:
- Maximum line length: 100 characters
- Use 2 spaces for indentation
- Use camelCase for functions and variables
- Use PascalCase for components
- Prefer const over let, never use var
- Use arrow functions
- Use functional components with hooks
Example:
/**
* Processes multiple images in parallel.
*
* @param {File[]} files - Array of image files to process
* @param {string[]} algorithms - Array of algorithm names to apply
* @returns {Promise<Object[]>} Array of processing results
*/
const processImages = async (files, algorithms) => {
const results = [];
for (const file of files) {
for (const algo of algorithms) {
try {
const result = await processImage(file, algo);
results.push({ success: true, file: file.name, algorithm: algo, result });
} catch (error) {
results.push({ success: false, file: file.name, algorithm: algo, error });
}
}
}
return results;
};Python:
# Standard library
import os
from pathlib import Path
# Third-party
import numpy as np
from PIL import Image
from flask import Flask, request
# Local
from dithering import floyd_steinberg_dither
from palettes import get_paletteJavaScript:
// React and third-party
import { useState, useEffect } from 'react';
import { Download, Upload } from 'lucide-react';
// Local components
import { Button } from './ui/button';
import { Card } from './ui/card';
// Utilities
import { cn } from '../lib/utils';Running Tests:
pytest tests/Running Specific Tests:
pytest tests/test_dithering.py
pytest tests/test_api.py -k test_floyd_steinbergWriting Tests:
import pytest
from PIL import Image
import numpy as np
from dithering import floyd_steinberg_dither
def test_floyd_steinberg_output():
"""Test that Floyd-Steinberg produces valid binary output."""
# Create test image
img = Image.new('RGB', (100, 100), color='gray')
# Apply dithering
result = floyd_steinberg_dither(img)
# Verify output
assert result.mode == 'L'
assert result.size == (100, 100)
# Check that output is binary (only 0 and 255)
arr = np.array(result)
unique_values = np.unique(arr)
assert set(unique_values) <= {0, 255}
def test_api_endpoint():
"""Test API endpoint returns correct response."""
# Test implementation
passRunning Tests:
npm testWriting Tests:
import { render, screen, fireEvent } from '@testing-library/react';
import { DitheringPanel } from './DitheringPanel';
describe('DitheringPanel', () => {
test('renders upload button', () => {
render(<DitheringPanel />);
const uploadButton = screen.getByText(/upload/i);
expect(uploadButton).toBeInTheDocument();
});
test('allows algorithm selection', () => {
render(<DitheringPanel />);
const checkbox = screen.getByLabelText(/floyd-steinberg/i);
fireEvent.click(checkbox);
expect(checkbox).toBeChecked();
});
});npm run buildThis creates optimized static files in the static/ directory.
- Build the frontend
- Set environment variables:
export FLASK_ENV=production - Run with a production WSGI server:
gunicorn app:app
See the CONTRIBUTING.md guide for detailed instructions on adding new dithering algorithms.
- Create component in
src/components/ui/ - Follow Radix UI patterns for accessibility
- Use Tailwind for styling
- Export from component file
- Add route in
api.py - Update API documentation
- Add tests for new endpoint
- Update frontend to use new endpoint
Backend:
# Enable Flask debug mode
app.run(debug=True)
# Add print statements or use pdb
import pdb; pdb.set_trace()Frontend:
// Use React DevTools browser extension
// Add console.log for debugging
console.log('Processing file:', file.name);
// Use debugger statement
debugger;- Use NumPy for array operations (much faster than Python loops)
- Profile with cProfile for bottlenecks
- Consider caching for repeated operations
- Use appropriate image formats (PNG for output)
- Lazy load components with React.lazy()
- Debounce expensive operations
- Use Web Workers for heavy processing
- Optimize bundle size with code splitting
Create a .env file for local development:
FLASK_ENV=development
FLASK_DEBUG=1
MAX_CONTENT_LENGTH=33554432 # 32MBDefault Ports:
- Flask backend: 5000
- Vite frontend: 5173
Changing Ports:
Backend (app.py):
app.run(port=5001)Frontend (vite.config.js):
export default {
server: {
port: 3000
}
}See TROUBLESHOOTING.md for solutions to common development issues.