Skip to content

Instantly share code, notes, and snippets.

@hiroshil
Created April 6, 2024 03:41
Show Gist options
  • Save hiroshil/c1d9dcc37eb8b268b14efc8881e3e05b to your computer and use it in GitHub Desktop.
Save hiroshil/c1d9dcc37eb8b268b14efc8881e3e05b to your computer and use it in GitHub Desktop.
Description is in the comment below
import sys
import numpy as np
from pathlib import Path
from PIL import Image
def process_file(file_path):
"""
Reads lines from a file, splits each line by dot, creates tuples with the first and second elements,
and appends them to a result array.
Args:
file_path (str): Path to the text file.
Returns:
list: A list of tuples containing the filename and coords from each line.
"""
result = []
with open(file_path, 'r') as f:
lines = f.readlines()[1:-1]
for line in lines:
# Split the line by dot and remove leading/trailing whitespace
elements = line.strip().split()
# Check if there are at least two elements
if len(elements) >= 2:
# Create a tuple with the first and second elements
elm5 = elements[5] # .replace('+PNAP+','#'): for GARbro
if Path(Path(base_img).parent / elm5).exists():
dict_data = { 'filename': elm5, 'coords' : (int(elements[1]), int(elements[2])) }
# Append the tuple to the result array
result.append(dict_data)
return result
def generate_unique_sequences(group_numbers):
"""
Generates sequences without repetition from groups of numbers,
ensuring each sequence has one element from each group.
Args:
group_numbers (list): A list of lists, where each sub-list represents a group of numbers.
Returns:
list: A list of unique sequences, each containing one element from each group.
"""
if len(group_numbers) < 2:
raise ValueError("Number of groups must be at least 2.")
# Check for empty groups and raise an error if any
for group in group_numbers:
if not group:
raise ValueError("No empty groups allowed.")
# Initialize an empty set to store unique sequences
sequences = list()
def backtrack(current_sequence, index):
"""
Recursive backtracking function to explore possible sequences.
Args:
current_sequence (list): Current sequence being built.
index (int): Current index indicating the group being considered.
"""
if index == len(group_numbers):
# Base case: Reached the end, add the sequence to the set
sequences.append(tuple(current_sequence))
return
for num in group_numbers[index]:
# Check if the current element is not already in the sequence
if num not in current_sequence:
# Add the element to the current sequence and explore further
current_sequence.append(num)
backtrack(current_sequence, index + 1)
# Backtrack: remove the element from the sequence for other possibilities
current_sequence.pop()
# Start backtracking from the first group with an empty sequence
backtrack([], 0)
# Convert the set of tuples to a list of lists for easier use
return list(sequences)
def overlay_image_alpha(img, img_overlay, x, y, alpha_mask):
"""Overlay `img_overlay` onto `img` at (x, y) and blend using `alpha_mask`.
`alpha_mask` must have same HxW as `img_overlay` and values in range [0, 1].
"""
# Image ranges
y1, y2 = max(0, y), min(img.shape[0], y + img_overlay.shape[0])
x1, x2 = max(0, x), min(img.shape[1], x + img_overlay.shape[1])
# Overlay ranges
y1o, y2o = max(0, -y), min(img_overlay.shape[0], img.shape[0] - y)
x1o, x2o = max(0, -x), min(img_overlay.shape[1], img.shape[1] - x)
# Exit if nothing to do
if y1 >= y2 or x1 >= x2 or y1o >= y2o or x1o >= x2o:
return
# Blend overlay within the determined ranges
img_crop = img[y1:y2, x1:x2]
img_overlay_crop = img_overlay[y1o:y2o, x1o:x2o]
alpha = alpha_mask[y1o:y2o, x1o:x2o, np.newaxis]
alpha_inv = 1.0 - alpha
img_crop[:] = alpha * img_overlay_crop + alpha_inv * img_crop
def overlay_images(overlay_layers, base_filename, output_filename):
"""
Overlays multiple images onto a base image using Blacktrack.
Args:
overlay_layers (list): A list of dict containing the filenames and coords of images to overlay.
base_filename (str): The filename of the base image.
output_filename (str): The filename of the output image file.
Returns:
None
"""
img = np.array(Image.open(base_filename))
img_result = img[:, :, :3].copy() # Create a copy for blending
for overlay in overlay_layers:
filename_path = Path(Path(base_filename).parent, overlay['filename'])
img_overlay_rgba = np.array(Image.open(filename_path))
# Assume all images have the same dimensions and overlay positions
# Perform blending
alpha_mask = img_overlay_rgba[:, :, 3] / 255.0
img_overlay = img_overlay_rgba[:, :, :3]
overlay_image_alpha(img_result, img_overlay, overlay['coords'][0], overlay['coords'][1], alpha_mask)
# Save the final result
Image.fromarray(img_result).save(output_filename)
file_path = '/Sys_title/Sys_title+PNAP+layers.txt'
base_img = '/Sys_title/Sys_title#059.png'
output_folder = 'output'
Path(output_folder).mkdir(parents=True, exist_ok=True)
layers = process_file(file_path)
matrix = []
if (len(sys.argv) < 2):
print("missing parameter")
else:
g = 1
is_array = True if (len(sys.argv) == 2) else False
if not is_array:
matrix.append([])
for layer in layers:
try:
matrix[g-1].append(layer)
except:
matrix.append((layer,)) # for sys img, pose,...
groupId = int(layer['filename'].split("#")[1][:-4])
if (groupId != int(sys.argv[-1]) and groupId == int(sys.argv[g])):
matrix.append([])
g += 1
if not is_array:
sequences = generate_unique_sequences(matrix)
overlay_images(sequences, base_img, Path(output_folder, Path(base_img).stem[:-3] + "_" + str(1).zfill(2) + ".png")) # for sys img, pose,...
else:
for i, seq in enumerate(matrix):
overlay_images(seq, base_img, Path(output_folder, Path(base_img).stem[:-3] + "_" + str(i+1).zfill(2) + ".png"))
@hiroshil
Copy link
Author

hiroshil commented Apr 6, 2024

Merge separated images output by AdvHD (coordinates and other information are already recorded in the corresponding <image name>.txt files)

Usage:

  1. txt file name: This is the name of the text file that contains the coordinates and other information for the separated images.
  2. Number of groups: This is the number of groups into which the images will be divided. One image will be selected from each group for merging.
  3. Maximum ID number for group 1: This is the maximum ID number for the images in group 1.
  4. Maximum ID number for group 2: This is the maximum ID number for the images in group 2.
  5. ... (fill in the corresponding IDs according to the number of groups)

Explanation:

Since different characters have different numbers of base images and expressions, it is necessary to specify their respective group IDs.

For example, if the character's illustration has:

  • IDs 1-5 (ID column in the txt file) as the character
  • IDs 8-9 as the blush
  • IDs 10-18 as the expression

Then the command would be:

Merge XXX.txt 3 5 9 18

In general, the IDs should be the upper limit for each group.

The code is inspired by the project: AdvHD-Merge2010. It was written by Google Bard and edited by me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment