summaryrefslogtreecommitdiffstats
path: root/Third_Party/generate_recursive_dithering.py
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2025-02-05 23:14:13 -0800
committeryum <yum.food.vr@gmail.com>2025-02-05 23:14:13 -0800
commit9b52e08a7f2400c7ee78e9b284ed0c4c876a7dd4 (patch)
tree601470be4190a95677e88c77c4c9846b0384a433 /Third_Party/generate_recursive_dithering.py
parentd45815e49ba24b881bf54b73fd1b8a82579fa1c2 (diff)
Add 4x4 textures to ssfd
Diffstat (limited to 'Third_Party/generate_recursive_dithering.py')
-rw-r--r--Third_Party/generate_recursive_dithering.py152
1 files changed, 91 insertions, 61 deletions
diff --git a/Third_Party/generate_recursive_dithering.py b/Third_Party/generate_recursive_dithering.py
index 417647e..c7f9248 100644
--- a/Third_Party/generate_recursive_dithering.py
+++ b/Third_Party/generate_recursive_dithering.py
@@ -3,86 +3,116 @@ 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)
+
+ # Handle edge case 1: circle is on left edge
+ if cx == 0:
+ cx2 = res
+ draw.ellipse([cx2-r_px, cy-r_px, cx2+r_px, cy+r_px], fill=255)
+ # Handle edge case 2: circle is on top edge
+ if cy == 0:
+ cy2 = res
+ draw.ellipse([cx-r_px, cy2-r_px, cx+r_px, cy2+r_px], fill=255)
+ # Handle edge case 3: circle is on top left corner
+ if cx == 0 and cy == 0:
+ cx2 = res
+ cy2 = res
+ draw.ellipse([cx2-r_px, cy2-r_px, cx2+r_px, cy2+r_px], fill=255)
return img
+def get_bayer_location(nth, res):
+ """
+ Returns the normalized location (x, y) for the nth dot in a recursive Bayer dithering pattern.
+
+ The pattern is constructed by writing nth in base-4 and mapping each digit as follows:
+ 0 -> (0, 0)
+ 1 -> (1, 1)
+ 2 -> (1, 0)
+ 3 -> (0, 1)
+
+ Each digit is weighted by successive powers of 1/2 so that:
+ x = sum_{i=0}^{k-1} (digit_x(i)) / 2^(i+1)
+ y = sum_{i=0}^{k-1} (digit_y(i)) / 2^(i+1)
+
+ The parameter `res` is expected to be a power-of-two (and in practice an even number); we let k = (res+1)//2.
+ For example:
+ - For res == 2, k = 1, and the function returns one of: (0,0), (0.5,0.5), (0.5,0), or (0,0.5).
+ - For res == 4, k = 2, and the function returns positions on a 4×4 grid.
+ - For res == 8, k = 4, and the function returns positions on a 16×16 grid.
+
+ If nth is greater than or equal to 4^k then an error is raised.
+ """
+ k = (res + 1) // 2 # determine the number of base-4 digits to use
+ if nth >= 4 ** k:
+ raise ValueError(f"nth value {nth} too large for given res {res} (max is {4**k - 1}).")
+
+ x, y = 0.0, 0.0
+ # Process each base-4 digit (least significant first)
+ for i in range(k):
+ digit = nth % 4
+ nth //= 4
+ weight = 1 / (2 ** (i + 1))
+ if digit == 0:
+ dx, dy = 0, 0
+ elif digit == 1:
+ dx, dy = 1, 1
+ elif digit == 2:
+ dx, dy = 1, 0
+ elif digit == 3:
+ dx, dy = 0, 1
+ else:
+ raise ValueError("Unexpected digit encountered while converting number to base-4")
+ x += dx * weight
+ y += dy * weight
+ return (x, y)
+
+
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
+ initial_area = 0.1
+
+ bayer_res = 16
+ total_layers = bayer_res * bayer_res
+ # Calculate number of digits needed for zero padding
+ num_digits = len(str(total_layers))
+
+ for layer in range(1, total_layers + 1):
+ # Calculate radius for this layer (split area among n circles)
+ radius = math.sqrt(initial_area/layer) * res
+
+ # Create the combined image for this layer
+ layer_img = Image.new('L', (res, res), 0)
+
+ # Place n circles according to Bayer pattern
+ for n in range(layer):
+ # Get normalized coordinates from Bayer pattern
+ x, y = get_bayer_location(n, bayer_res)
+ # Convert to pixel coordinates
+ center = (x * res, y * res)
+ # Create and add circle
+ circle = create_circle_image(res, radius/res, center=center)
- 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")
+ layer_img = Image.fromarray(np.maximum(np.array(layer_img), np.array(circle)))
+
+ # Save the layer with zero-padded number
+ layer_img.save(f"dots_L{layer:0{num_digits}d}.png")
if __name__ == "__main__":
- main() \ No newline at end of file
+ main()