← back to Resize It
scripts/resize_image.py
183 lines
#!/usr/bin/env python3
"""
Image resizing script for Resize It skill
Resizes images to 24" width at 150 DPI while maintaining aspect ratio
"""
import sys
import json
from pathlib import Path
from PIL import Image
import argparse
def calculate_dimensions(target_width_inches=24, dpi=150):
"""Calculate target pixel dimensions"""
target_width_px = int(target_width_inches * dpi)
return target_width_px
def analyze_resize_quality(original_width, original_height, target_width):
"""Analyze if resize will degrade quality"""
scale_factor = target_width / original_width
result = {
"original_width": original_width,
"original_height": original_height,
"target_width": target_width,
"target_height": int(original_height * scale_factor),
"scale_factor": scale_factor,
"is_upscaling": scale_factor > 1,
"quality_warning": None
}
if scale_factor > 1:
upscale_percentage = (scale_factor - 1) * 100
result["quality_warning"] = (
f"⚠️ WARNING: Image will be upscaled by {upscale_percentage:.1f}%. "
f"This may result in quality degradation and pixelation. "
f"Original: {original_width}x{original_height}px, "
f"Target: {result['target_width']}x{result['target_height']}px"
)
elif scale_factor < 0.5:
downscale_percentage = (1 - scale_factor) * 100
result["quality_warning"] = (
f"ℹ️ INFO: Image will be significantly downscaled by {downscale_percentage:.1f}%. "
f"Some detail will be lost. "
f"Original: {original_width}x{original_height}px, "
f"Target: {result['target_width']}x{result['target_height']}px"
)
return result
def resize_image(input_path, output_path=None, target_width_inches=24, dpi=150, quality=95):
"""
Resize image to specified width in inches at given DPI
Args:
input_path: Path to input image
output_path: Path for output image (optional, will generate if not provided)
target_width_inches: Target width in inches (default 24")
dpi: Dots per inch (default 150)
quality: JPEG quality 1-100 (default 95)
Returns:
dict with result information
"""
try:
# Open image
img = Image.open(input_path)
original_format = img.format
original_width, original_height = img.size
# Calculate target dimensions
target_width_px = calculate_dimensions(target_width_inches, dpi)
# Analyze quality impact
analysis = analyze_resize_quality(original_width, original_height, target_width_px)
# Calculate target height maintaining aspect ratio
aspect_ratio = original_height / original_width
target_height_px = int(target_width_px * aspect_ratio)
# Perform resize using high-quality resampling
# Try newer API first, fall back to older API
try:
resample_method = Image.Resampling.LANCZOS
except AttributeError:
resample_method = Image.LANCZOS
resized_img = img.resize((target_width_px, target_height_px), resample_method)
# Generate output path if not provided
if output_path is None:
input_path_obj = Path(input_path)
output_path = str(input_path_obj.parent / f"{input_path_obj.stem}_24x{target_height_px//dpi}in_150dpi{input_path_obj.suffix}")
# Save with DPI metadata
save_kwargs = {'dpi': (dpi, dpi)}
# Add format-specific options
if original_format in ['JPEG', 'JPG'] or output_path.lower().endswith(('.jpg', '.jpeg')):
save_kwargs['quality'] = quality
save_kwargs['optimize'] = True
elif original_format == 'PNG' or output_path.lower().endswith('.png'):
save_kwargs['optimize'] = True
resized_img.save(output_path, **save_kwargs)
# Build result
result = {
"success": True,
"input_path": str(input_path),
"output_path": str(output_path),
"original_dimensions": {"width": original_width, "height": original_height},
"output_dimensions": {"width": target_width_px, "height": target_height_px},
"physical_dimensions": {
"width_inches": target_width_inches,
"height_inches": target_height_px / dpi,
"dpi": dpi
},
"scale_factor": analysis["scale_factor"],
"quality_warning": analysis["quality_warning"]
}
return result
except Exception as e:
return {
"success": False,
"error": str(e),
"input_path": str(input_path)
}
def main():
parser = argparse.ArgumentParser(
description='Resize image to 24" width at 150 DPI maintaining aspect ratio'
)
parser.add_argument('input', help='Input image path')
parser.add_argument('-o', '--output', help='Output image path (optional)')
parser.add_argument('-w', '--width', type=float, default=24,
help='Target width in inches (default: 24)')
parser.add_argument('-d', '--dpi', type=int, default=150,
help='DPI resolution (default: 150)')
parser.add_argument('-q', '--quality', type=int, default=95,
help='JPEG quality 1-100 (default: 95)')
parser.add_argument('--json', action='store_true',
help='Output result as JSON')
args = parser.parse_args()
# Perform resize
result = resize_image(
args.input,
args.output,
args.width,
args.dpi,
args.quality
)
if args.json:
print(json.dumps(result, indent=2))
else:
if result["success"]:
print(f"✅ Image resized successfully!")
print(f" Input: {result['input_path']}")
print(f" Output: {result['output_path']}")
print(f" Original: {result['original_dimensions']['width']}x{result['original_dimensions']['height']}px")
print(f" Resized: {result['output_dimensions']['width']}x{result['output_dimensions']['height']}px")
print(f" Physical: {result['physical_dimensions']['width_inches']:.1f}\" x {result['physical_dimensions']['height_inches']:.1f}\" @ {result['physical_dimensions']['dpi']} DPI")
print(f" Scale: {result['scale_factor']:.2f}x")
if result.get('quality_warning'):
print(f"\n{result['quality_warning']}")
else:
print(f"❌ Error: {result['error']}")
sys.exit(1)
if __name__ == '__main__':
main()