Pixdither __link__ Instant

#!/usr/bin/env python3 """ pixdither - Image dithering tool with Floyd-Steinberg algorithm Converts images to reduced color palettes using error diffusion """

class PixDither: def __init__(self, image_path, output_path=None, bits_per_channel=1, palette_type="monochrome", dither_algorithm="floyd-steinberg"): """ Initialize dithering processor Args: image_path: Path to input image output_path: Path for output image (optional) bits_per_channel: Color depth (1-8 bits per channel) palette_type: "monochrome", "grayscale", "rgb", or custom dither_algorithm: "floyd-steinberg", "atkinson", or "none" """ self.image_path = Path(image_path) self.output_path = output_path self.bits = bits_per_channel self.palette_type = palette_type self.algorithm = dither_algorithm # Load image self.img = Image.open(image_path).convert('RGB') self.pixels = np.array(self.img, dtype=np.float32) self.height, self.width = self.pixels.shape[:2] def quantize_color(self, color): """Quantize a single RGB color based on bits per channel""" if self.palette_type == "monochrome": # Simple black/white based on luminance luminance = 0.299 * color[0] + 0.587 * color[1] + 0.114 * color[2] return np.array([255, 255, 255]) if luminance > 127 else np.array([0, 0, 0]) elif self.palette_type == "grayscale": # Grayscale with 2^bits levels levels = 2 ** self.bits step = 256 / levels luminance = 0.299 * color[0] + 0.587 * color[1] + 0.114 * color[2] gray_level = round(luminance / step) * step return np.array([gray_level, gray_level, gray_level]) else: # RGB quantization levels = 2 ** self.bits step = 256 / levels quantized = np.round(color / step) * step return np.clip(quantized, 0, 255) def floyd_steinberg(self): """Apply Floyd-Steinberg dithering""" result = self.pixels.copy() for y in range(self.height): for x in range(self.width): old_pixel = result[y, x].copy() new_pixel = self.quantize_color(old_pixel) result[y, x] = new_pixel error = old_pixel - new_pixel # Distribute error to neighboring pixels if x + 1 < self.width: result[y, x + 1] += error * 7/16 if y + 1 < self.height: if x > 0: result[y + 1, x - 1] += error * 3/16 result[y + 1, x] += error * 5/16 if x + 1 < self.width: result[y + 1, x + 1] += error * 1/16 return np.clip(result, 0, 255).astype(np.uint8) def atkinson(self): """Apply Atkinson dithering""" result = self.pixels.copy() for y in range(self.height): for x in range(self.width): old_pixel = result[y, x].copy() new_pixel = self.quantize_color(old_pixel) result[y, x] = new_pixel error = old_pixel - new_pixel # Distribute error (all divided by 8) if x + 1 < self.width: result[y, x + 1] += error * 1/8 if x + 2 < self.width: result[y, x + 2] += error * 1/8 if y + 1 < self.height: if x > 0: result[y + 1, x - 1] += error * 1/8 result[y + 1, x] += error * 1/8 if x + 1 < self.width: result[y + 1, x + 1] += error * 1/8 if y + 2 < self.height: result[y + 2, x] += error * 1/8 return np.clip(result, 0, 255).astype(np.uint8) def simple_quantize(self): """Simple quantization without dithering""" result = self.pixels.copy() for y in range(self.height): for x in range(self.width): result[y, x] = self.quantize_color(result[y, x]) return result.astype(np.uint8) def process(self): """Process the image with selected algorithm""" print(f"Processing: {self.image_path.name}") print(f" Size: {self.width}x{self.height}") print(f" Palette: {self.palette_type}") print(f" Bits per channel: {self.bits}") print(f" Algorithm: {self.algorithm}") if self.algorithm == "floyd-steinberg": output_pixels = self.floyd_steinberg() elif self.algorithm == "atkinson": output_pixels = self.atkinson() elif self.algorithm == "none": output_pixels = self.simple_quantize() else: raise ValueError(f"Unknown algorithm: {self.algorithm}") # Create output image output_img = Image.fromarray(output_pixels, 'RGB') # Save or return if self.output_path: output_img.save(self.output_path) print(f" Saved to: {self.output_path}") else: # Auto-generate output filename output_path = self.image_path.stem + f"_dithered{self.image_path.suffix}" output_img.save(output_path) print(f" Saved to: {output_path}") return output_img pixdither

import argparse import sys from PIL import Image import numpy as np from pathlib import Path or custom dither_algorithm: "floyd-steinberg"