def main(): parser = argparse.ArgumentParser( description='pixdither - Apply dithering to images', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: pixdither input.jpg -o output.png # Default (black/white Floyd-Steinberg) pixdither input.jpg -b 3 -p grayscale # 3-bit grayscale pixdither input.jpg -b 2 -p rgb -a atkinson # 2-bit per channel RGB with Atkinson pixdither input.jpg --no-dither # Simple quantization without dithering pixdither input.jpg --gif output.gif # Create animated dithering GIF """ ) parser.add_argument('input', help='Input image path') parser.add_argument('-o', '--output', help='Output image path') parser.add_argument('-b', '--bits', type=int, default=1, help='Bits per channel (1-8, default: 1)') parser.add_argument('-p', '--palette', choices=['monochrome', 'grayscale', 'rgb'], default='monochrome', help='Color palette type (default: monochrome)') parser.add_argument('-a', '--algorithm', choices=['floyd-steinberg', 'atkinson', 'none'], default='floyd-steinberg', help='Dithering algorithm (default: floyd-steinberg)') parser.add_argument('--gif', help='Create animated dithering GIF (provide output path)') args = parser.parse_args() # Validate bits if args.bits < 1 or args.bits > 8: print("Error: bits must be between 1 and 8") sys.exit(1) # Check if creating GIF if args.gif: create_gif(args.input, args.gif) return # Process single image try: dithered = PixDither( args.input, args.output, bits_per_channel=args.bits, palette_type=args.palette, dither_algorithm=args.algorithm ) dithered.process() except FileNotFoundError: print(f"Error: File '{args.input}' not found") sys.exit(1) except Exception as e: print(f"Error: {e}") sys.exit(1)
import argparse import sys from PIL import Image import numpy as np from pathlib import Path pixdither
def create_gif(input_path, output_path, frames=10, duration=0.1): """Create animated dithering GIF showing progression""" from PIL import ImageDraw, ImageFont images = [] base_img = Image.open(input_path).convert('RGB') for i in range(1, frames + 1): bits = max(1, int(8 * i / frames)) dithered = PixDither(input_path, bits_per_channel=bits, palette_type="rgb", dither_algorithm="floyd-steinberg") img = dithered.process() # Add text overlay draw = ImageDraw.Draw(img) try: font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20) except: font = ImageFont.load_default() draw.text((10, 10), f"{bits} bits/channel", fill=(255, 255, 255), font=font) images.append(img) images[0].save(output_path, save_all=True, append_images=images[1:], duration=duration*1000, loop=0) print(f"GIF saved to: {output_path}") def main(): parser = argparse