Files

201 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""Command-line interface for pixel sorting."""
import argparse
import sys
from pathlib import Path
from PIL import Image
from pixelsort import pixelsort, analyze, INTERVAL_FUNCTIONS, SORTING_FUNCTIONS
def print_analysis(stats):
"""Pretty print analysis results."""
brightness_stats = stats['brightness_stats']
pixel_int_stats = stats['pixel_int_stats']
suggestions = stats['suggestions']
print("=" * 60)
print("BRIGHTNESS ANALYSIS (0-255 scale)")
print("=" * 60)
print(f" Min: {brightness_stats['min']:.1f}")
print(f" Max: {brightness_stats['max']:.1f}")
print(f" Mean: {brightness_stats['mean']:.1f}")
print(f" Median: {brightness_stats['median']:.1f}")
print()
print(" Percentiles:")
print(f" 10%: {brightness_stats['p10']:.1f}")
print(f" 25%: {brightness_stats['p25']:.1f}")
print(f" 50%: {brightness_stats['median']:.1f} (median)")
print(f" 75%: {brightness_stats['p75']:.1f}")
print(f" 90%: {brightness_stats['p90']:.1f}")
print()
print("=" * 60)
print("PIXEL INTEGER ANALYSIS (signed 32-bit)")
print("=" * 60)
print(f" Min: {pixel_int_stats['min']:,}")
print(f" Max: {pixel_int_stats['max']:,}")
print(f" Median: {pixel_int_stats['median']:,}")
print()
print(" Percentiles:")
print(f" 25%: {pixel_int_stats['p25']:,}")
print(f" 50%: {pixel_int_stats['median']:,} (median)")
print(f" 75%: {pixel_int_stats['p75']:,}")
print()
print("=" * 60)
print("SUGGESTED THRESHOLDS")
print("=" * 60)
print()
print("For --interval bright (sorts pixels above threshold):")
print(f" Subtle (top 25%): --bright-value {suggestions['bright']['subtle']}")
print(f" Moderate (top 50%): --bright-value {suggestions['bright']['moderate']}")
print(f" Aggressive (top 75%): --bright-value {suggestions['bright']['aggressive']}")
print()
print("For --interval dark (sorts pixels below threshold):")
print(f" Subtle (bottom 25%): --dark-value {suggestions['dark']['subtle']}")
print(f" Moderate (bottom 50%): --dark-value {suggestions['dark']['moderate']}")
print(f" Aggressive (bottom 75%): --dark-value {suggestions['dark']['aggressive']}")
print()
print("For --interval white (sorts pixels above threshold):")
print(f" Subtle (top 25%): --white-value {suggestions['white']['subtle']}")
print(f" Moderate (top 50%): --white-value {suggestions['white']['moderate']}")
print(f" Aggressive (top 75%): --white-value {suggestions['white']['aggressive']}")
print()
print("For --interval black (sorts pixels below threshold):")
print(f" Subtle (bottom 25%): --black-value {suggestions['black']['subtle']}")
print(f" Moderate (bottom 50%): --black-value {suggestions['black']['moderate']}")
print(f" Aggressive (bottom 75%): --black-value {suggestions['black']['aggressive']}")
print()
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description='Pixel sorting with ASDF and extended modes',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# ASDF-style modes (classic)
python main.py input.jpg --interval bright
python main.py input.jpg --interval dark --dark-value 50
# New interval modes
python main.py input.jpg --interval random --char-length 100
python main.py input.jpg --interval edges
python main.py input.jpg --interval waves --char-length 75
# Different sorting functions
python main.py input.jpg --interval bright --sort hue
python main.py input.jpg --interval threshold --sort saturation
# Angle and randomness
python main.py input.jpg --interval bright --angle 45
python main.py input.jpg --interval random --randomness 20
# Analyze image first
python main.py input.jpg --analyze
"""
)
parser.add_argument('input', type=str, help='Input image path')
parser.add_argument('--analyze', action='store_true',
help='Analyze image and suggest thresholds (does not sort)')
# Interval mode
parser.add_argument('-i', '--interval', type=str, default='bright',
choices=list(INTERVAL_FUNCTIONS.keys()),
help='Interval function (default: bright)')
# Sorting function
parser.add_argument('-s', '--sort', type=str, default='value',
choices=list(SORTING_FUNCTIONS.keys()),
help='Sorting function (default: value)')
# Output
parser.add_argument('-o', '--output', type=str, help='Output image path')
# Parameters
parser.add_argument('-a', '--angle', type=float, default=0,
help='Angle in degrees (default: 0)')
parser.add_argument('-r', '--randomness', type=float, default=0,
help='Randomness percentage 0-100 (default: 0)')
parser.add_argument('-c', '--char-length', type=int, default=50,
help='Characteristic length for random/waves (default: 50)')
parser.add_argument('-l', '--loops', type=int, default=1,
help='Number of sorting passes (default: 1)')
# Threshold values
parser.add_argument('--lower-threshold', type=float, default=0.25,
help='Lower threshold 0-1 for threshold mode (default: 0.25)')
parser.add_argument('--upper-threshold', type=float, default=0.8,
help='Upper threshold 0-1 for threshold mode (default: 0.8)')
parser.add_argument('--white-value', type=int, default=-12345678,
help='White threshold for white mode (default: -12345678)')
parser.add_argument('--black-value', type=int, default=-3456789,
help='Black threshold for black mode (default: -3456789)')
parser.add_argument('--bright-value', type=int, default=127,
help='Brightness threshold for bright mode (default: 127)')
parser.add_argument('--dark-value', type=int, default=223,
help='Darkness threshold for dark mode (default: 223)')
args = parser.parse_args()
# Load image
input_path = Path(args.input)
if not input_path.exists():
print(f"Error: Input file '{args.input}' not found")
return 1
print(f"Loading image: {args.input}")
img = Image.open(input_path)
# If analyze mode, run analysis and exit
if args.analyze:
stats = analyze(img)
print_analysis(stats)
return 0
# Determine output path
if args.output:
output_path = Path(args.output)
else:
output_path = input_path.with_name(f"{input_path.stem}_{args.interval}_{args.sort}.png")
# Sort the image
print(f"Sorting with interval={args.interval}, sorting={args.sort}")
if args.angle != 0:
print(f" Angle: {args.angle}°")
if args.randomness > 0:
print(f" Randomness: {args.randomness}%")
if args.loops > 1:
print(f" Loops: {args.loops}")
result = pixelsort(
img,
interval=args.interval,
sorting=args.sort,
angle=args.angle,
randomness=args.randomness,
char_length=args.char_length,
loops=args.loops,
lower_threshold=args.lower_threshold,
upper_threshold=args.upper_threshold,
white_value=args.white_value,
black_value=args.black_value,
bright_value=args.bright_value,
dark_value=args.dark_value,
)
# Save output
print(f"Saving to: {output_path}")
result.save(output_path)
print("Done!")
return 0
if __name__ == "__main__":
sys.exit(main())