Skip to content

Instantly share code, notes, and snippets.

@imaurer
Created April 2, 2025 15:30
Show Gist options
  • Save imaurer/85c937514664cf1183b50af55cd88596 to your computer and use it in GitHub Desktop.
Save imaurer/85c937514664cf1183b50af55cd88596 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S uv --quiet run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "pyahocorasick",
# ]
# ///
"""
Icon Slicer Script
Processes an input image by converting its background to transparent using ImageMagick
and then slicing it into individual icons based on contiguous non-transparent regions.
Requirements:
- Python 3.11+
- opencv-python
- numpy
- ImageMagick (the "magick" command must be in your PATH)
"""
import cv2
import numpy as np
import subprocess
import sys
from pathlib import Path
import argparse
import shutil
def check_imagemagick():
"""Check if the ImageMagick 'magick' command is available."""
if shutil.which("magick") is None:
sys.exit(
"Error: ImageMagick 'magick' command not found. Please install ImageMagick and ensure it's in your PATH."
)
def process_image(
input_path: Path,
output_folder: Path,
fuzz: str = "5%",
min_area: int = 100,
dilation_size: int = 3,
):
"""
Processes an input image: makes its background transparent, saves the transparent version,
and slices it into individual icons based on opaque regions.
Args:
input_path (Path): Path to the input image file.
output_folder (Path): Path to the folder where output files will be saved.
fuzz (str): Fuzz factor for background color matching (default: "5%").
min_area (int): Minimum area (in pixels) for a contour to be considered an icon (default: 100).
dilation_size (int): Kernel size for the dilation operation (default: 3).
"""
output_folder.mkdir(parents=True, exist_ok=True)
# Read the input image
img = cv2.imread(str(input_path))
if img is None:
raise FileNotFoundError(f"Image not found at {input_path}")
# Get the background color from the top-left pixel (BGR format)
b, g, r = img[0, 0]
color = f"rgb({r},{g},{b})" # Convert to RGB for ImageMagick
# Define output path for the full transparent image
transparent_path = output_folder / "transparent_full.png"
# Use ImageMagick to make the background transparent
try:
subprocess.run(
[
"magick",
str(input_path),
"-fuzz",
fuzz,
"-transparent",
color,
str(transparent_path),
],
check=True,
)
except subprocess.CalledProcessError as e:
raise RuntimeError("ImageMagick command failed") from e
# Read the transparent image with the alpha channel
transparent_img = cv2.imread(str(transparent_path), cv2.IMREAD_UNCHANGED)
if transparent_img is None:
raise FileNotFoundError(f"Transparent image not found at {transparent_path}")
if transparent_img.shape[2] < 4:
raise ValueError(
"The processed image does not contain an alpha channel. Background transparency may have failed."
)
# Extract the alpha channel and create a binary mask (opaque = 255, transparent = 0)
alpha = transparent_img[:, :, 3]
_, binary = cv2.threshold(alpha, 1, 255, cv2.THRESH_BINARY)
# Dilate the mask to connect nearby opaque regions
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (dilation_size, dilation_size))
dilated = cv2.dilate(binary, kernel, iterations=1)
# Find contours of the opaque regions
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Sort contours top-to-bottom, then left-to-right
contours = sorted(
contours, key=lambda c: (cv2.boundingRect(c)[1], cv2.boundingRect(c)[0])
)
# Crop and save each detected icon
icon_count = 1
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if w * h >= min_area:
cropped = transparent_img[y : y + h, x : x + w]
output_filename = output_folder / f"icon_{icon_count:02d}.png"
cv2.imwrite(str(output_filename), cropped)
icon_count += 1
return transparent_path, icon_count - 1
def parse_args():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="Process an image to make its background transparent and slice it into icons."
)
parser.add_argument("input", type=Path, help="Path to the input image file")
parser.add_argument("output", type=Path, help="Path to the output folder")
parser.add_argument(
"--fuzz", default="5%", help="Fuzz factor for ImageMagick (default: 5%)"
)
parser.add_argument(
"--min-area",
type=int,
default=100,
help="Minimum area for a contour to be considered an icon (default: 100)",
)
parser.add_argument(
"--dilation-size",
type=int,
default=3,
help="Kernel size for dilation (default: 3)",
)
return parser.parse_args()
def main():
args = parse_args()
check_imagemagick()
try:
transparent_path, icon_count = process_image(
args.input, args.output, args.fuzz, args.min_area, args.dilation_size
)
print(f"Processed image saved to {transparent_path}")
print(f"Extracted {icon_count} icon(s) in {args.output}")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment