commit 34c677e7470a3859e8ee75c0196b83e83b7f2592 Author: wangziao <1575538687@qq.com> Date: Sat Apr 19 23:56:29 2025 -0700 upload files diff --git a/color_rotate.PNG b/color_rotate.PNG new file mode 100644 index 0000000..06b9900 Binary files /dev/null and b/color_rotate.PNG differ diff --git a/color_rotate.gif b/color_rotate.gif new file mode 100644 index 0000000..51f4e54 Binary files /dev/null and b/color_rotate.gif differ diff --git a/grid.jpg b/grid.jpg new file mode 100644 index 0000000..4e77352 Binary files /dev/null and b/grid.jpg differ diff --git a/grid.mp4 b/grid.mp4 new file mode 100644 index 0000000..131d209 Binary files /dev/null and b/grid.mp4 differ diff --git a/map2video.py b/map2video.py new file mode 100644 index 0000000..808c8a1 --- /dev/null +++ b/map2video.py @@ -0,0 +1,99 @@ +from PIL import Image +import imageio + + +# Function to map (x, y) to (u, v) +def map_function(x, y, scale_s, scale_t, mapparam): + # You can define your mapping function here. + # For example, this function scales and shifts the coordinates: + u = mapparam * (scale_s*scale_t) * x / (x*x+y*y) + v = mapparam * (scale_s*scale_t) * y / (x*x+y*y) + return u, v + +def create_frames(stepsize,N_pics,w,h,wt,ht,mapparam): + default_color = (0, 0, 0) + output_images = [] + scale_s = min(w, h) + scale_t = min(wt, ht) + + for iter in range(N_pics): + print("%d\r"%iter,end="") + offset = -iter*stepsize + def wrap(int_u): + return (int_u+offset) % (wt - 1) + + output_image = Image.new("RGB", (w, h)) + for x in range(w): + for y in range(h): + realx, realy = x-w//2, y-h//2 + if realx==0 and realy==0: + output_image.putpixel((x,y), default_color) + continue + realu, realv = map_function(realx, realy, scale_s, scale_t, mapparam) + u, v = realu + wt//2, realv + ht//2 + + # Interpolation (you can use different methods, such as bilinear) + u_floor, v_floor = int(u), int(v) + u_frac, v_frac = u - u_floor, v - v_floor + + # Ensure u and v are within the bounds of the texture image + if u_floor < 0 or u_floor > wt - 2 or v_floor < 0 or v_floor > ht - 2: + output_image.putpixel((x,y), default_color) + continue + + color_top_left = texture_image.getpixel((wrap(u_floor), v_floor)) + color_top_right = texture_image.getpixel((wrap(u_floor + 1), v_floor)) + color_bottom_left = texture_image.getpixel((wrap(u_floor), v_floor + 1)) + color_bottom_right = texture_image.getpixel((wrap(u_floor + 1), v_floor + 1)) + + # Interpolate colors + color = ( + int((1 - u_frac) * (1 - v_frac) * color_top_left[0] + + u_frac * (1 - v_frac) * color_top_right[0] + + (1 - u_frac) * v_frac * color_bottom_left[0] + + u_frac * v_frac * color_bottom_right[0]), + int((1 - u_frac) * (1 - v_frac) * color_top_left[1] + + u_frac * (1 - v_frac) * color_top_right[1] + + (1 - u_frac) * v_frac * color_bottom_left[1] + + u_frac * v_frac * color_bottom_right[1]), + int((1 - u_frac) * (1 - v_frac) * color_top_left[2] + + u_frac * (1 - v_frac) * color_top_right[2] + + (1 - u_frac) * v_frac * color_bottom_left[2] + + u_frac * v_frac * color_bottom_right[2]) + ) + + output_image.putpixel((x, y), color) + output_images.append(output_image) + + return output_images + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(description='Turn an image into a stylized gif where every frame of the gif is\ + a conformal mapping of the original image shifted and wrapped around in the width direction') + parser.add_argument('input_file') + parser.add_argument('output_file') + parser.add_argument('--output_width', type=int, default=400) + parser.add_argument('--output_height', type=int, default=400) + parser.add_argument('--num_frames', type=int, default=60) + parser.add_argument('--x_shift_per_frame', type=int, nargs="?") + parser.add_argument('--mapping_param', type=float, default=0.05) + args = parser.parse_args() + + # Load your texture image + texture_image = Image.open(args.input_file) + + # Set the width and height of the output image + w, h = args.output_width, args.output_height + # Set the width and height of the texture image + wt, ht = texture_image.width, texture_image.height + + mapparam = args.mapping_param + N_pics = args.num_frames + if args.x_shift_per_frame==None: + stepsize = wt / N_pics # one whole period + else: + stepsize = args.x_shift_per_frame + + output_images = create_frames(stepsize,N_pics,w,h,wt,ht,mapparam) + imageio.mimsave(args.output_file, output_images) \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e6c0061 --- /dev/null +++ b/readme.md @@ -0,0 +1,25 @@ +# Conformal Mapping and Video Generation + +## What is the mapping? + +``` +u = mapparam * (scale_s*scale_t) * x / (x*x+y*y) +v = mapparam * (scale_s*scale_t) * y / (x*x+y*y) +``` + +is equivalent to the normalized version: + +``` +u/scale_t = mapparam * (x/scale_s) / ((x/scale_s)**2 + (y/scale_s)**2) +v/scale_t = mapparam * (y/scale_s) / ((x/scale_s)**2 + (y/scale_s)**2) +``` + +This explains why there is a "hole" in the output image: small normalized x,y maps to big normalized u,v. If the normalized u,v has an absolute value greater than 0.5, there is no corresponding pixel in the texture input file, we output the default color black. + +## How to get the example results + +Environment: Python 3.11. Do `pip install -r requirements.txt` to get necessary packages. + +`python map2video.py color_rotate.png color_rotate.gif --num_frames 40` + +`python map2video.py grid.jpg grid.mp4 --num_frames 40` \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..326d3aa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pillow==9.3.0 +imageio==2.31.5 +imageio-ffmpeg==0.4.9 \ No newline at end of file