diff --git a/Dockerfile b/Dockerfile index ef192ca..3247bdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ RUN apt-get update && apt-get -y install python3-dev python3-pip chafa imagemagi RUN mkdir /opt/imgrot COPY . /opt/imgrot RUN pip3 install -r /opt/imgrot/requirements.txt --break-system-packages -ENTRYPOINT [ "python3", "/opt/imgrot/demo.py" ] +ENTRYPOINT [ "python3", "/opt/imgrot/imgrot.py" ] diff --git a/Makefile b/Makefile index cc2bb2f..1e117a4 100644 --- a/Makefile +++ b/Makefile @@ -26,19 +26,19 @@ clean: docker.clean py-clean docs: docs.vhs docs.jinja #docs.rotations docs.jinja: ${pynchon.run} jinja render README.md.j2 - python demo.py img/icon.png --stream > img/demo.gif + python imgrot.py img/icon.png --stream > img/demo.gif docs.vhs:; PS1="$$ " sh -c "${pynchon.run} vhs apply" docs.rotations: - python demo.py img/graph.png --bg lightblue --rotation x --stream > img/rx.gif - python demo.py img/graph.png --bg lightblue --rotation y --stream > img/ry.gif - python demo.py img/graph.png --bg lightblue --rotation s --stream > img/rs.gif - python demo.py img/graph.png --bg lightblue --rotation j --stream > img/rj.gif - python demo.py img/graph.png --bg lightblue --rotation w --stream > img/rw.gif - python demo.py img/graph.png --bg lightblue --rotation f --stream > img/rf.gif - python demo.py img/graph.png --bg lightblue --rotation exit-ul --stream > img/rul.gif - python demo.py img/graph.png --bg lightblue --rotation exit-ur --stream > img/rur.gif - python demo.py img/graph.png --bg lightblue --rotation exit-lr --stream > img/rlr.gif - python demo.py img/graph.png --bg lightblue --rotation exit-ll --stream > img/rll.gif + python imgrot.py img/graph.png --bg lightblue --rotation x --stream > img/rx.gif + python imgrot.py img/graph.png --bg lightblue --rotation y --stream > img/ry.gif + python imgrot.py img/graph.png --bg lightblue --rotation s --stream > img/rs.gif + python imgrot.py img/graph.png --bg lightblue --rotation j --stream > img/rj.gif + python imgrot.py img/graph.png --bg lightblue --rotation w --stream > img/rw.gif + python imgrot.py img/graph.png --bg lightblue --rotation f --stream > img/rf.gif + python imgrot.py img/graph.png --bg lightblue --rotation exit-ul --stream > img/rul.gif + python imgrot.py img/graph.png --bg lightblue --rotation exit-ur --stream > img/rur.gif + python imgrot.py img/graph.png --bg lightblue --rotation exit-lr --stream > img/rlr.gif + python imgrot.py img/graph.png --bg lightblue --rotation exit-ll --stream > img/rll.gif docker.clean: diff --git a/README.md b/README.md index e8afee4..8b2502b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@
-**Rendering a gif, then displaying it in a terminal-friendly way with chafa:**
+#### Terminal-Friendly Display
```bash
$ docker run -it --rm -v `pwd`:/workspace -w /workspace robotwranglers/imgrot img/icon.png --display --stretch --bg lightblue
@@ -98,11 +98,11 @@ $ docker run -it --rm -v `pwd`:/workspace -w /workspace robotwranglers/imgrot im
-Note that this tries to respect transparency in the original image, but for more contrast you can effectively add highlights by passing '--bg' arguments that go through to chafa.
+Note that this tries to respect transparency in the original image, but for more contrast with black images on black terminals, you can effectively add highlights by passing '--bg' arguments that go through to chafa.
------------------------------
-**Changing axis of rotation**
+#### Changing Axis of Rotation
The rotation can be controlled to create a bunch of different effects:
diff --git a/README.md.j2 b/README.md.j2
index e847f80..bcc9b66 100644
--- a/README.md.j2
+++ b/README.md.j2
@@ -3,7 +3,8 @@
-**Rendering a gif, then displaying it in a terminal-friendly way with chafa:**
+#### Terminal-Friendly Display
```bash
$ docker run -it --rm -v `pwd`:/workspace -w /workspace robotwranglers/imgrot img/icon.png --display --stretch --bg lightblue
@@ -79,11 +79,11 @@ $ docker run -it --rm -v `pwd`:/workspace -w /workspace robotwranglers/imgrot im
-Note that this tries to respect transparency in the original image, but for more contrast you can effectively add highlights by passing '--bg' arguments that go through to chafa.
+Note that this tries to respect transparency in the original image, but for more contrast with black images on black terminals, you can effectively add highlights by passing '--bg' arguments that go through to chafa.
------------------------------
-**Changing axis of rotation**
+#### Changing Axis of Rotation
The rotation can be controlled to create a bunch of different effects:
diff --git a/docs/README.original.md b/docs/README.original.md
index 49dd1cc..731cb7f 100644
--- a/docs/README.original.md
+++ b/docs/README.original.md
@@ -19,7 +19,7 @@ Rotate along XZ axis
Change main function with ideal [arguments](#parameters)
```bash
-python demo.py [path of the image] [degree to rotate] ([ideal width] [ideal height])
+python imgrot.py [path of the image] [degree to rotate] ([ideal width] [ideal height])
```
e.g.,
Example of rotating an image along yz-axis from 0 to 360 degree with a 5 pixel shift in +X direction
@@ -32,7 +32,7 @@ Example of rotating an image along yz-axis from 0 to 360 degree with a 5 pixel s
```
Then
```bash
-python demo.py img/000001.jpg 360
+python imgrot.py img/000001.jpg 360
```
## Parameters:
diff --git a/docs/tape/demo.tape b/docs/tape/demo.tape
index e443186..f5629d3 100644
--- a/docs/tape/demo.tape
+++ b/docs/tape/demo.tape
@@ -9,7 +9,7 @@ Set Height 800
Set TypingSpeed .05
Set PlaybackSpeed 1
Set CursorBlink false
-Type "python demo.py img/icon.png --display --stretch --bg lightblue"
+Type "python imgrot.py img/icon.png --display --stretch --bg lightblue"
Sleep 1.1
Enter
Sleep 30
diff --git a/image_transformer.py b/image_transformer.py
index 31d0409..bc154ee 100644
--- a/image_transformer.py
+++ b/image_transformer.py
@@ -1,8 +1,3 @@
-from math import pi
-
-import cv2
-import numpy as np
-
# Usage:
# Change main function with ideal arguments
# Then
@@ -25,6 +20,13 @@
# 1. : http://stackoverflow.com/questions/17087446/how-to-calculate-perspective-transform-for-opencv-from-rotation-angles
# 2. : http://jepsonsblog.blogspot.tw/2012/11/rotation-in-3d-using-opencvs.html
+import math
+import random
+from math import pi
+
+import cv2
+import numpy as np
+
def get_rad(theta, phi, gamma):
return (deg_to_rad(theta), deg_to_rad(phi), deg_to_rad(gamma))
@@ -43,16 +45,20 @@ def rad_to_deg(rad):
class ImageTransformer:
- """Perspective transformation class for image
- with shape (height, width, #channels)"""
+ """
+ Perspective transformation class for image
+ with shape (height, width, #channels)
+ """
def load_image(self, img_path, shape=None):
+ """ """
img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
if shape is not None:
img = cv2.resize(img, shape)
return img
def __init__(self, image_path, shape):
+ """ """
self.image_path = image_path
self.image = self.load_image(image_path, shape)
@@ -60,12 +66,81 @@ def __init__(self, image_path, shape):
self.width = self.image.shape[1]
self.num_channels = self.image.shape[2]
+ def get_rotation_args(self, rotation: str, ang: int):
+ """
+ Generates the arguments used for 'rotate_along_axis'
+ This takes a rotation-mode 'rotation' and an angle
+ named 'ang' in (0 - rot_range), and is called from a loop.
+ """
+ if rotation in ["y"]:
+ # y-axis from 0-360 degree, 5 pixel shift in +X
+ rotargs = dict(phi=ang, dx=5)
+ elif rotation in ["x"]:
+ rotargs = dict(gamma=ang)
+ elif rotation in ["s", "swivel"]:
+ # yz-axis from 0 to 360 degree
+ rotargs = dict(phi=ang, gamma=ang)
+ elif rotation in ["jitter", "j"]:
+ rotargs = dict(
+ dx=random.choice([5, 0, 10]),
+ phi=random.choice([ang, -ang]),
+ gamma=random.choice([0, ang, -ang]),
+ )
+ elif rotation in ["wobble", "w"]:
+ rotargs = dict(
+ dx=random.choice([0, 10, 30]),
+ dy=random.choice([25, 0, 10]),
+ phi=random.choice([ang, -ang, math.sin(ang)]),
+ gamma=random.choice([ang, -ang]),
+ )
+ elif rotation in ["f", "flip"]:
+ rotargs = dict(dx=ang, dy=-ang, phi=math.tan(ang), gamma=ang)
+ elif rotation.startswith("q"):
+ rotargs = dict(
+ dx=random.choice([0, 5, 10]),
+ dy=random.choice([25, 0, 10]),
+ phi=random.choice([ang, -ang, math.sin(ang)]),
+ gamma=random.choice([ang, -ang]),
+ )
+ elif rotation.startswith("exit"):
+ direction = rotation.split("-")[1]
+ if direction == "ul":
+ rotargs = dict(
+ dx=-ang,
+ dy=-ang,
+ )
+ elif direction == "ur":
+ rotargs = dict(
+ dx=ang,
+ dy=-ang,
+ )
+ elif direction == "lr":
+ rotargs = dict(
+ dx=ang,
+ dy=ang,
+ )
+ elif direction == "ll":
+ rotargs = dict(
+ dx=-ang,
+ dy=ang,
+ )
+ else:
+ raise ValueError(f"unknown rotation: {rotation}")
+ rotargs.update(phi=math.tan(ang), gamma=math.tan(ang))
+ else:
+ raise ValueError(f"Not sure how to perform rotation {rotation}")
+ return rotargs
+
@staticmethod
def save_image(img_path, img):
+ """
+ Saves an output image
+ This is usually one that's resulted from a rotation, i.e a single frame in a larger animation
+ """
cv2.imwrite(img_path, img)
def rotate_along_axis(self, theta=0, phi=0, gamma=0, dx=0, dy=0, dz=0):
- """Wrapper for rotating a image"""
+ """Returns the new image that results from rotating self.image"""
# Get radius of rotation along 3 axes
rtheta, rphi, rgamma = get_rad(theta, phi, gamma)
@@ -78,13 +153,10 @@ def rotate_along_axis(self, theta=0, phi=0, gamma=0, dx=0, dy=0, dz=0):
# Get projection matrix
mat = self.get_M(rtheta, rphi, rgamma, dx, dy, dz)
-
return cv2.warpPerspective(self.image.copy(), mat, (self.width, self.height))
- """ Get Perspective Projection Matrix """
-
def get_M(self, theta, phi, gamma, dx, dy, dz):
-
+ """Get Perspective Projection Matrix"""
w = self.width
h = self.height
f = self.focal
diff --git a/img/demo.chafa.gif b/img/demo.chafa.gif
index 0719118..1b3c1bf 100644
Binary files a/img/demo.chafa.gif and b/img/demo.chafa.gif differ
diff --git a/demo.py b/imgrot.py
similarity index 64%
rename from demo.py
rename to imgrot.py
index c1437f2..9f56ed2 100644
--- a/demo.py
+++ b/imgrot.py
@@ -2,10 +2,10 @@
# Usage:
# Change main function with ideal arguments
# then
-# python demo.py [name of the image] [degree to rotate] ([ideal width] [ideal height])
+# python imgrot.py [name of the image] [degree to rotate] ([ideal width] [ideal height])
# e.g.,
-# python demo.py img/000001.jpg 360
-# python demo.py img/000001.jpg 45 500 700
+# python imgrot.py img/000001.jpg 360
+# python imgrot.py img/000001.jpg 45 500 700
#
# Parameters:
# img_path : path of image that you want rotated
@@ -27,15 +27,17 @@
from image_transformer import ImageTransformer
-# Read log level from environment variable
+# Setup logging
log_level = os.getenv("LOGLEVEL", "INFO").upper()
logging.basicConfig(
level=getattr(logging, log_level, logging.INFO),
format="%(asctime)s - imgrot - %(levelname)s - %(message)s",
+ handlers=[logging.StreamHandler(sys.stderr)],
)
logger = logging.getLogger(__name__)
+# Setup CLI interface
@click.command()
@click.option("--bg", default="black", help="Background color to pass to chafa")
@click.option(
@@ -104,84 +106,19 @@ def run(
duration = f"--duration {duration}" if duration else ""
invert = invert and "--invert" or ""
bg = f"--bg {bg}"
- import math
if not os.path.exists(img_path):
logger.debug(f"{img_path} does not exist!")
raise SystemExit(1)
- # img_path_base=os.path.basename(img_path)
- # err = os.system(f"convert {img_path} -fuzz 10% -transparent white -alpha off /tmp/{img_path_base}")
- # if err: raise SystemExit(1)
- # img_path = f"/tmp/{img_path_base}"
-
rot_range = int(rot_range)
img_shape = img_shape and list(map(int, img_shape.split("x")))
it = ImageTransformer(img_path, img_shape)
if not os.path.isdir(output_dir):
os.mkdir(output_dir)
logger.debug(f"Rotating {img_path} .. ")
- import random
-
for ang in range(0, rot_range):
- if rotation in ["y"]:
- # y-axis from 0-360 degree, 5 pixel shift in +X
- rotargs = dict(phi=ang, dx=5)
- elif rotation in ["x"]:
- rotargs = dict(gamma=ang)
- elif rotation in ["s", "swivel"]:
- # yz-axis from 0 to 360 degree
- rotargs = dict(phi=ang, gamma=ang)
- elif rotation in ["jitter", "j"]:
- rotargs = dict(
- dx=random.choice([5, 0, 10]),
- phi=random.choice([ang, -ang]),
- gamma=random.choice([0, ang, -ang]),
- )
- elif rotation in ["wobble", "w"]:
- rotargs = dict(
- dx=random.choice([0, 10, 30]),
- dy=random.choice([25, 0, 10]),
- phi=random.choice([ang, -ang, math.sin(ang)]),
- gamma=random.choice([ang, -ang]),
- )
- elif rotation in ["f", "flip"]:
- rotargs = dict(dx=ang, dy=-ang, phi=math.tan(ang), gamma=ang)
- elif rotation.startswith("q"):
- rotargs = dict(
- dx=random.choice([0, 5, 10]),
- dy=random.choice([25, 0, 10]),
- phi=random.choice([ang, -ang, math.sin(ang)]),
- gamma=random.choice([ang, -ang]),
- )
- elif rotation.startswith("exit"):
- direction = rotation.split("-")[1]
- if direction == "ul":
- rotargs = dict(
- dx=-ang,
- dy=-ang,
- )
- elif direction == "ur":
- rotargs = dict(
- dx=ang,
- dy=-ang,
- )
- elif direction == "lr":
- rotargs = dict(
- dx=ang,
- dy=ang,
- )
- elif direction == "ll":
- rotargs = dict(
- dx=-ang,
- dy=ang,
- )
- else:
- raise ValueError(f"unknown rotation: {rotation}")
- rotargs.update(phi=math.tan(ang), gamma=math.tan(ang))
- else:
- raise ValueError(f"Not sure how to perform rotation {rotation}")
- rotated_img = it.rotate_along_axis(**rotargs)
+ rotated_img = it.rotate_along_axis(**it.get_rotation_args(rotation, ang))
fname = f"{output_dir}/{str(ang).zfill(3)}.png"
it.save_image(fname, rotated_img)
logger.debug("Done")
@@ -201,10 +138,10 @@ def run(
f"""ffmpeg -y -i /tmp/.tmp.gif -vf "split[s0][s1];[s0]palettegen[p];[s1]setpts={speed}*PTS[v];[v][p]paletteuse" {output_file} {quiet_maybe}""",
]
for cmd in commands:
- logger.warning(cmd)
+ logger.debug(cmd)
result = subprocess.run(cmd, shell=True, stdout=sys.stderr, stderr=sys.stderr)
if result.returncode != 0:
- logger.debug(f"Command failed with return code {result.returncode}")
+ logger.critical(f"Command failed with return code {result.returncode}")
raise SystemExit(result.returncode)
if view:
logger.debug(f"Viewing {img_path}")
@@ -223,7 +160,7 @@ def run(
content = binary_file.read()
sys.stdout.buffer.write(content)
else:
- logger.debug("No instructions, not sure what to do")
+ logger.critical("No instructions, not sure what to do")
raise SystemExit(1)