Skip to content

Instantly share code, notes, and snippets.

@UserUnknownFactor
Created August 22, 2024 12:00
Show Gist options
  • Save UserUnknownFactor/8d855994b68d1d53b016e84c47ed28b7 to your computer and use it in GitHub Desktop.
Save UserUnknownFactor/8d855994b68d1d53b016e84c47ed28b7 to your computer and use it in GitHub Desktop.
Python script for automatic diffing of PNGs
# -*- coding: utf-8 -*-
# This script finds all PNGs of similar looks/size in the specified
# folder and produces a base image and a series of diffs with it.
import os
import sys
from PIL import Image
import numpy as np
from collections import defaultdict
DIFF_THRESHOLD = 0 # Adjust this based on diff sensitivity needed
SIMILARITY_THRESHOLD = 10 # Adjust this to control how images are clustered
terminal_width = 80 # Fallback width
try:
terminal_width = os.get_terminal_size().columns
except:
pass
def image_similarity(img1, img2):
"""Calculates the average pixel difference between two images."""
return np.mean(np.abs(np.array(img1) - np.array(img2)))
def group_by_dims_and_similarity(images):
"""Groups images by dimensions first, then by similarity."""
groups_by_dimension = defaultdict(list)
for img in images:
groups_by_dimension[img.size].append(img)
final_groups = {}
group_id = 0
for size, images_with_same_size in groups_by_dimension.items():
similarity_groups = defaultdict(list)
for img in images_with_same_size:
added_to_group = False
for existing_group_id, group_images in similarity_groups.items():
similarity = image_similarity(img, group_images[0])
print(similarity)
if similarity <= SIMILARITY_THRESHOLD:
similarity_groups[existing_group_id].append(img)
added_to_group = True
break
if not added_to_group:
similarity_groups[len(similarity_groups)].append(img)
# Add the similarity groups to the final groups
for _, group in similarity_groups.items():
final_groups[group_id] = group
group_id += 1
return final_groups
def find_base_image(images):
np_images = [np.array(img.convert('RGBA')) for img in images]
base_image_array = np.median(np.stack(np_images), axis=0).astype(np.uint8)
return Image.fromarray(base_image_array, 'RGBA')
def compute_difference(base, img):
base_array = np.array(base)
img_array = np.array(img)
diff = np.abs(base_array - img_array)
mask = (diff.sum(axis=2) <= DIFF_THRESHOLD)
img_array[mask] = 0 # Set RGBA to 0 where there's no difference
return Image.fromarray(img_array, 'RGBA')
def process_images(directory):
images = []
for file in os.listdir(directory):
if file.endswith('.png') and not file.startswith('base_') and not file.startswith('diff_'):
img = Image.open(os.path.join(directory, file))
images.append(img)
grouped_images = group_by_dims_and_similarity(images)
for group_id, img_group in grouped_images.items():
print(f"Processing image group {group_id}")
base_image = find_base_image(img_group)
base_name = f'base_group_{group_id}'
base_image.save(f'{base_name}.png')
#max_digits = max(3, math.ceil(math.log10(len(img_group))))
for i, img in enumerate(img_group):
filename = os.path.basename(img.filename)
padding = max(0, terminal_width - len(filename) - 12)
proc_text = f'Processing: {filename}'
sys.stdout.write(f"{proc_text}{' ' * (padding - len(proc_text))}\r")
sys.stdout.flush()
diff_img = compute_difference(base_image, img)
diff_img.save(f'diff_{os.path.splitext(filename)[0]}_{base_name}.png', optimize=True)
print(f"Finished {' ' * (terminal_width - 9)}")
process_images('.')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment