From fb26b02a20ef751cdd1832abc925b5e57bb2234b Mon Sep 17 00:00:00 2001 From: yum Date: Sat, 1 Feb 2025 19:15:58 -0800 Subject: Implement surface stable fractal dithering --- Third_Party/generate_recursive_dithering.py | 88 ++++++++++++++++++++++++++ Third_Party/shift_image_to_corner.py | 97 +++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 Third_Party/generate_recursive_dithering.py create mode 100644 Third_Party/shift_image_to_corner.py (limited to 'Third_Party') diff --git a/Third_Party/generate_recursive_dithering.py b/Third_Party/generate_recursive_dithering.py new file mode 100644 index 0000000..417647e --- /dev/null +++ b/Third_Party/generate_recursive_dithering.py @@ -0,0 +1,88 @@ +import numpy as np +from PIL import Image, ImageDraw +import argparse +import math + +from shift_image_to_corner import shift_to_corner + +def create_circle_image(res, radius, center=None): + """Create a binary image with a white circle on black background.""" + if center is None: + center = (res/2, res/2) + + img = Image.new('L', (res, res), 0) + draw = ImageDraw.Draw(img) + + # Convert radius and center to pixels + r_px = int(radius * res) + cx, cy = int(center[0]), int(center[1]) + + # Draw white circle (255) + draw.ellipse([cx-r_px, cy-r_px, cx+r_px, cy+r_px], fill=255) + return img + +def main(): + parser = argparse.ArgumentParser(description="Generate dot and circle images.") + parser.add_argument("--res", type=int, default=128, help="Resolution of the images.") + args = parser.parse_args() + + res = args.res + goal_area = 0.1 + # a = pi * r^2 + # r = sqrt(a / pi) + initial_r = math.sqrt(goal_area / math.pi) * res # Start with a circle that fills 1/4 of the image + + # Calculate areas and radii for each layer + initial_area = math.pi * (initial_r/res)**2 + + # Layer 1: Single circle in corner + r1 = math.sqrt(initial_area/1) * res # Calculate radius consistently with other layers + base_img = create_circle_image(res, r1/res) + corner_img = shift_to_corner(base_img) + corner_img.save("dots_L1.png") + + # Layer 2: Two circles, split area + r2 = math.sqrt(initial_area/2) * res # New radius for each circle + # Create corner circle + corner_img = shift_to_corner(create_circle_image(res, r2/res)) + # Create center circle + center_img = create_circle_image(res, r2/res) + # Combine images + layer2 = Image.fromarray(np.maximum(np.array(corner_img), np.array(center_img))) + layer2.save("dots_L2.png") + + # Layer 3: Three circles + r3 = math.sqrt(initial_area/3) * res + # Create corner and center circles as before + corner_img = shift_to_corner(create_circle_image(res, r3/res)) + center_img = create_circle_image(res, r3/res) + # Create top circle + top_img = shift_to_corner(create_circle_image(res, r3/res), shift_side=False) + # Combine images + layer3 = Image.fromarray(np.maximum.reduce([ + np.array(corner_img), + np.array(center_img), + np.array(top_img) + ])) + layer3.save("dots_L3.png") + + # Layer 4: Four circles + r4 = math.sqrt(initial_area/4) * res + # Create corner and center circles + corner_img = shift_to_corner(create_circle_image(res, r4/res)) + center_img = create_circle_image(res, r4/res) + # Create top and right circles + top_img = shift_to_corner(create_circle_image(res, r4/res), shift_side=False) + right_img = shift_to_corner(create_circle_image(res, r4/res), shift_up=False) + # Combine images + + layer4 = Image.fromarray(np.maximum.reduce([ + np.array(corner_img), + np.array(center_img), + np.array(top_img), + np.array(right_img) + ])) + layer4.save("dots_L4.png") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Third_Party/shift_image_to_corner.py b/Third_Party/shift_image_to_corner.py new file mode 100644 index 0000000..8f365a7 --- /dev/null +++ b/Third_Party/shift_image_to_corner.py @@ -0,0 +1,97 @@ +""" +Shift image to corner, wrapping it toroidally. +""" + +from PIL import Image +import numpy as np +import argparse +import os + +def shift_to_corner(img, shift_side=True, shift_up=True): + """ + Shifts an image to edges with toroidal wrapping based on specified directions. + + Args: + img (PIL.Image): Input Pillow image + shift_side (bool): Whether to shift horizontally to the right edge + shift_up (bool): Whether to shift vertically to the top edge + + Returns: + PIL.Image: Shifted image + """ + # Convert image to numpy array + img_array = np.array(img) + + # Get dimensions + height, width = img_array.shape[:2] + half_height = height // 2 + half_width = width // 2 + + # Create new array for the shifted image + shifted = np.zeros_like(img_array) + + if shift_side and shift_up: + # Original behavior - shift to upper right corner + shifted[half_height:, half_width:] = img_array[:half_height, :half_width] # Q1 -> BR + shifted[half_height:, :half_width] = img_array[:half_height, half_width:] # Q2 -> BL + shifted[:half_height, half_width:] = img_array[half_height:, :half_width] # Q3 -> TR + shifted[:half_height, :half_width] = img_array[half_height:, half_width:] # Q4 -> TL + elif shift_side: + # Only shift horizontally to right + shifted[:, half_width:] = img_array[:, :half_width] # Left half -> Right + shifted[:, :half_width] = img_array[:, half_width:] # Right half -> Left + elif shift_up: + # Only shift vertically to top + shifted[half_height:, :] = img_array[:half_height, :] # Top half -> Bottom + shifted[:half_height, :] = img_array[half_height:, :] # Bottom half -> Top + else: + # No shift, return original image + shifted = img_array.copy() + + # Convert back to PIL Image and return + return Image.fromarray(shifted) + +def shift_to_corner_from_file(image_path, output_path, shift_side=True, shift_up=True): + """ + Wrapper function that shifts an image file to edges with toroidal wrapping. + + Args: + image_path (str): Path to the input image + output_path (str): Path where the shifted image will be saved + shift_side (bool): Whether to shift horizontally to the right edge + shift_up (bool): Whether to shift vertically to the top edge + + Returns: + PIL.Image: Shifted image + """ + img = Image.open(image_path) + result = shift_to_corner(img, shift_side=shift_side, shift_up=shift_up) + result.save(output_path) + return result + +def get_output_path(input_path): + """ + Generate output path by adding '_shifted' before the file extension. + + Args: + input_path (str): Path to the input image + Returns: + str: Path for the output image + """ + base, ext = os.path.splitext(input_path) + return f"{base}_shifted{ext}" + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Shift an image to the corner with toroidal wrapping.') + parser.add_argument('input_image', help='Path to the input image file') + parser.add_argument('--no-side', action='store_false', dest='shift_side', + help='Disable horizontal shifting (default: enabled)') + parser.add_argument('--no-up', action='store_false', dest='shift_up', + help='Disable vertical shifting (default: enabled)') + + args = parser.parse_args() + output_path = get_output_path(args.input_image) + + shift_to_corner_from_file(args.input_image, output_path, + shift_side=args.shift_side, + shift_up=args.shift_up) -- cgit v1.2.3