{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "4a6a2e38-7d25-4818-9d76-e6fe919df86f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import clear_output, Image as IPImage\n",
    "from PIL import Image, ImageDraw, ImageMath, ImageChops\n",
    "from typing import List\n",
    "import numpy as np\n",
    "import math\n",
    "import random"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "90085e18-30ec-441f-bf5c-0069db3eb99b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def rotation_matrix(axis, theta):\n",
    "    \"\"\"\n",
    "    Return the rotation matrix associated with counterclockwise rotation about\n",
    "    the given axis by theta radians.\n",
    "    \"\"\"\n",
    "    axis = np.asarray(axis)\n",
    "    axis = axis / math.sqrt(np.dot(axis, axis))\n",
    "    a = math.cos(theta / 2.0)\n",
    "    b, c, d = -axis * math.sin(theta / 2.0)\n",
    "    aa, bb, cc, dd = a * a, b * b, c * c, d * d\n",
    "    bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d\n",
    "    return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],\n",
    "                     [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],\n",
    "                     [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f8069be0-c494-44e8-bc66-ba08051b83a5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Point2d:\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "27f4d619-14d7-4f00-95bb-a4f1a93f0aab",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Point3d:\n",
    "    def __init__(self, x, y, z):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "        self.z = z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4deac1bf-7c15-4530-acbc-b3a7256725c5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Face:\n",
    "    def __init__(self, points: List[Point3d], color: str):\n",
    "        self.points = points\n",
    "        self.color = color"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "41f45d62-074c-4be0-a817-001909cc73d2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Perspective:\n",
    "    def __init__(self, center: Point2d, strength):\n",
    "        self.center = center\n",
    "        self.strength = strength\n",
    "    \n",
    "    def projectPoint(self, point: Point3d):\n",
    "        return Point2d(\n",
    "            #(point.x + (point.x-self.center.x) * (self.strength/10000) * math.log(point.z)),\n",
    "            #(point.y + (point.y-self.center.y) * (self.strength/10000) * math.log(point.z)),\n",
    "            #(point.x + (point.x-self.center.x) * (self.strength/10000) * point.z),\n",
    "            #(point.y + (point.y-self.center.y) * (self.strength/10000) * point.z),\n",
    "            #self.strength * (point.x + (point.x-self.center.x)) / point.z,\n",
    "            #self.strength * (point.y + (point.y-self.center.y)) / point.z,\n",
    "            \n",
    "            (point.x + (point.x-self.center.x) * (self.strength/10000) * (point.z+1e2)/1e2),\n",
    "            (point.y + (point.y-self.center.y) * (self.strength/10000) * (point.z+1e2)/1e2),\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "5e1d1210-a590-4eab-9c68-80f91d8a7445",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Rotation:\n",
    "    def __init__(self, axis, theta):\n",
    "        self.axis = axis\n",
    "        self.theta = theta\n",
    "    \n",
    "    def rotatePoint(self, point: Point3d):\n",
    "        matrix = [point.x, point.y, point.z]\n",
    "        matrix = np.dot(rotation_matrix(self.axis, self.theta), matrix)\n",
    "        return Point3d(matrix[0], matrix[1], matrix[2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "01659240-e447-46fb-a6d3-53aed9853e45",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "square = (\n",
    "    # back\n",
    "    Face((\n",
    "        Point3d( 50,  50,  -50),\n",
    "        Point3d( 50, 150,  -50),\n",
    "        Point3d(150, 150,  -50),\n",
    "        Point3d(150,  50,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    \n",
    "    # left\n",
    "    Face((\n",
    "        Point3d( 50,  50,   50),\n",
    "        Point3d( 50,  50,  -50),\n",
    "        Point3d( 50, 150,  -50),\n",
    "        Point3d( 50, 150,   50),\n",
    "    ), (127, 255, 0, 170)),\n",
    "    \n",
    "    # bottom\n",
    "    Face((\n",
    "        Point3d( 50, 150,   50),\n",
    "        Point3d(150, 150,   50),\n",
    "        Point3d(150, 150,  -50),\n",
    "        Point3d( 50, 150,  -50),\n",
    "    ), (127, 0, 255, 170)),\n",
    "    \n",
    "    # front\n",
    "    Face((\n",
    "        Point3d( 50,  50,  50),\n",
    "        Point3d( 50, 150,  50),\n",
    "        Point3d(150, 150,  50),\n",
    "        Point3d(150,  50,  50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    \n",
    "    # right\n",
    "    Face((\n",
    "        Point3d(150,  50,   50),\n",
    "        Point3d(150,  50,  -50),\n",
    "        Point3d(150, 150,  -50),\n",
    "        Point3d(150, 150,   50),\n",
    "    ), (127, 255, 0, 170)),\n",
    "    \n",
    "    # top\n",
    "    Face((\n",
    "        Point3d( 50,  50,   50),\n",
    "        Point3d(150,  50,   50),\n",
    "        Point3d(150,  50,  -50),\n",
    "        Point3d( 50,  50,  -50),\n",
    "    ), (127, 0, 255, 170)),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fb166b8a-9e51-48e2-8695-a92b86b402e5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "letterL = (\n",
    "    # back\n",
    "    Face((\n",
    "        Point3d( 50,  50,  -50),\n",
    "        Point3d( 50, 150,  -50),\n",
    "        Point3d(150, 150,  -50),\n",
    "        Point3d(150, 112,  -50),\n",
    "        Point3d(100, 112,  -50),\n",
    "        Point3d(100,  50,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    \n",
    "    # left\n",
    "    Face((\n",
    "        Point3d( 50,  50,   50),\n",
    "        Point3d( 50,  50,  -50),\n",
    "        Point3d( 50, 150,  -50),\n",
    "        Point3d( 50, 150,   50),\n",
    "    ), (127, 255, 0, 170)),\n",
    "    \n",
    "    # bottom\n",
    "    Face((\n",
    "        Point3d( 50, 150,   50),\n",
    "        Point3d(150, 150,   50),\n",
    "        Point3d(150, 150,  -50),\n",
    "        Point3d( 50, 150,  -50),\n",
    "    ), (127, 0, 255, 170)),\n",
    "    \n",
    "    # front\n",
    "    Face((\n",
    "        Point3d( 50,  50,   50),\n",
    "        Point3d( 50, 150,   50),\n",
    "        Point3d(150, 150,   50),\n",
    "        Point3d(150, 112,   50),\n",
    "        Point3d(100, 112,   50),\n",
    "        Point3d(100,  50,   50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    \n",
    "    # right\n",
    "    Face((\n",
    "        Point3d(100,  50,   50),\n",
    "        Point3d(100,  50,  -50),\n",
    "        Point3d(100, 112,  -50),\n",
    "        Point3d(100, 112,   50),\n",
    "    ), (127, 255, 0, 170)),\n",
    "    Face((\n",
    "        Point3d(150, 112,   50),\n",
    "        Point3d(150, 112,  -50),\n",
    "        Point3d(150, 150,  -50),\n",
    "        Point3d(150, 150,   50),\n",
    "    ), (127, 255, 0, 170)),\n",
    "    \n",
    "    # top\n",
    "    Face((\n",
    "        Point3d( 50,  50,   50),\n",
    "        Point3d(100,  50,   50),\n",
    "        Point3d(100,  50,  -50),\n",
    "        Point3d( 50,  50,  -50),\n",
    "    ), (127, 0, 255, 170)),\n",
    "    Face((\n",
    "        Point3d(100, 112,   50),\n",
    "        Point3d(150, 112,   50),\n",
    "        Point3d(150, 112,  -50),\n",
    "        Point3d(100, 112,  -50),\n",
    "    ), (127, 0, 255, 170)),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "48eb5e60-f650-4f60-a4de-6ae94f59c876",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "square2 = (\n",
    "    # back\n",
    "    Face((\n",
    "        Point3d(170,  50,  -50),\n",
    "        Point3d(170, 150,  -50),\n",
    "        Point3d(270, 150,  -50),\n",
    "        Point3d(270,  50,  -50),\n",
    "    ), (0, 255, 127, 170)),\n",
    "    \n",
    "    # left\n",
    "    Face((\n",
    "        Point3d(170,  50,   50),\n",
    "        Point3d(170,  50,  -50),\n",
    "        Point3d(170, 150,  -50),\n",
    "        Point3d(170, 150,   50),\n",
    "    ), (255, 0, 127, 170)),\n",
    "    \n",
    "    # bottom\n",
    "    Face((\n",
    "        Point3d(170, 150,   50),\n",
    "        Point3d(270, 150,   50),\n",
    "        Point3d(270, 150,  -50),\n",
    "        Point3d(170, 150,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    \n",
    "    # front\n",
    "    Face((\n",
    "        Point3d(170,  50,  50),\n",
    "        Point3d(170, 150,  50),\n",
    "        Point3d(270, 150,  50),\n",
    "        Point3d(270,  50,  50),\n",
    "    ), (0, 255, 127, 170)),\n",
    "    \n",
    "    # right\n",
    "    Face((\n",
    "        Point3d(270,  50,   50),\n",
    "        Point3d(270,  50,  -50),\n",
    "        Point3d(270, 150,  -50),\n",
    "        Point3d(270, 150,   50),\n",
    "    ), (255, 0, 127, 170)),\n",
    "    \n",
    "    # top\n",
    "    Face((\n",
    "        Point3d(170,  50,   50),\n",
    "        Point3d(270,  50,   50),\n",
    "        Point3d(270,  50,  -50),\n",
    "        Point3d(170,  50,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5089b488-0c18-47ca-b9f0-24ff1fb62998",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "letterF = (\n",
    "    # back\n",
    "    Face((\n",
    "        Point3d(170,  50,  -50),\n",
    "        Point3d(170, 150,  -50),\n",
    "        Point3d(220, 150,  -50),\n",
    "        Point3d(220, 120,  -50),\n",
    "        Point3d(245, 120,  -50),\n",
    "        Point3d(245,  88,  -50),\n",
    "        Point3d(270,  88,  -50),\n",
    "        Point3d(270,  50,  -50),\n",
    "    ), (0, 255, 127, 170)),\n",
    "    \n",
    "    # left\n",
    "    Face((\n",
    "        Point3d(170,  50,   50),\n",
    "        Point3d(170,  50,  -50),\n",
    "        Point3d(170, 150,  -50),\n",
    "        Point3d(170, 150,   50),\n",
    "    ), (255, 0, 127, 170)),\n",
    "    \n",
    "    # bottom\n",
    "    Face((\n",
    "        Point3d(170, 150,   50),\n",
    "        Point3d(220, 150,   50),\n",
    "        Point3d(220, 150,  -50),\n",
    "        Point3d(170, 150,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    Face((\n",
    "        Point3d(220, 120,   50),\n",
    "        Point3d(245, 120,   50),\n",
    "        Point3d(245, 120,  -50),\n",
    "        Point3d(220, 120,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    Face((\n",
    "        Point3d(245,  88,   50),\n",
    "        Point3d(270,  88,   50),\n",
    "        Point3d(270,  88,  -50),\n",
    "        Point3d(245,  88,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    "    \n",
    "    # front\n",
    "    Face((\n",
    "        Point3d(170,  50,   50),\n",
    "        Point3d(170, 150,   50),\n",
    "        Point3d(220, 150,   50),\n",
    "        Point3d(220, 120,   50),\n",
    "        Point3d(245, 120,   50),\n",
    "        Point3d(245,  88,   50),\n",
    "        Point3d(270,  88,   50),\n",
    "        Point3d(270,  50,   50),\n",
    "    ), (0, 255, 127, 170)),\n",
    "    \n",
    "    # right\n",
    "    Face((\n",
    "        Point3d(270,  50,   50),\n",
    "        Point3d(270,  50,  -50),\n",
    "        Point3d(270,  88,  -50),\n",
    "        Point3d(270,  88,   50),\n",
    "    ), (255, 0, 127, 170)),\n",
    "    Face((\n",
    "        Point3d(245,  88,   50),\n",
    "        Point3d(245,  88,  -50),\n",
    "        Point3d(245, 120,  -50),\n",
    "        Point3d(245, 120,   50),\n",
    "    ), (255, 0, 127, 170)),\n",
    "    Face((\n",
    "        Point3d(220, 120,   50),\n",
    "        Point3d(220, 120,  -50),\n",
    "        Point3d(220, 150,  -50),\n",
    "        Point3d(220, 150,   50),\n",
    "    ), (255, 0, 127, 170)),\n",
    "    \n",
    "    # top\n",
    "    Face((\n",
    "        Point3d(170,  50,   50),\n",
    "        Point3d(270,  50,   50),\n",
    "        Point3d(270,  50,  -50),\n",
    "        Point3d(170,  50,  -50),\n",
    "    ), (255, 127, 0, 170)),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "cabbc0a8-51eb-43ed-9d63-545f4fdf846b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def drawPoint2d(x, y):\n",
    "    draw.ellipse((x-2, y-2, x+2, y+2), fill = None, outline = 'black')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "57cfe910-3417-4957-99bd-00d64cbd613c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def drawPoint3d(x, y, z, perspective):\n",
    "    x = perspective.transformPoint(x)\n",
    "    y = perspective.transformPoint(y)\n",
    "    drawPoint2d(x, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "8129122a-804c-45d2-b4f7-68264f9e98ce",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def drawLine(pointA: Point2d, pointB: Point2d):\n",
    "    draw.line([(pointA.x, pointA.y), (pointB.x, pointB.y)], fill = 'red', width = 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "2e6a3406-4646-40f6-8092-885b4cb1a25a",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def drawFace(face: Face, perspective: Perspective, rotation: Rotation, pivot: Point3d, translation: Point3d, scale: Point3d):\n",
    "    pairs = []\n",
    "    for point in face.points:\n",
    "        movedPoint = Point3d(\n",
    "            (point.x - pivot.x) * scale.x,\n",
    "            (point.y - pivot.y) * scale.y,\n",
    "            (point.z - pivot.z) * scale.z,\n",
    "        )\n",
    "        tilt = Rotation([1, 0, 0], 0.3)\n",
    "        movedPoint = tilt.rotatePoint(movedPoint)\n",
    "        point3d = rotation.rotatePoint(movedPoint)\n",
    "        movedPoint = Point3d(\n",
    "            point3d.x + translation.x,\n",
    "            point3d.y + translation.y,\n",
    "            point3d.z + translation.z,\n",
    "        )\n",
    "        point2d = perspective.projectPoint(movedPoint)\n",
    "        pairs = pairs + [point2d.x, point2d.y]\n",
    "        drawPoint2d(point2d.x, point2d.y)\n",
    "    draw.polygon(pairs, fill = face.color, outline = 'black', width = 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "8c525323-2ccf-4d52-89b8-3c6458de5532",
   "metadata": {},
   "outputs": [],
   "source": [
    "def drawObject(obj: List[Face], perspective: Perspective, rotation: Rotation, pivot: Point3d, translation: Point3d, scale: Point3d):\n",
    "    zsorted = sorted(obj, key=lambda face: (rotation.rotatePoint(face.points[0]).z + rotation.rotatePoint(face.points[1]).z + rotation.rotatePoint(face.points[2]).z + rotation.rotatePoint(face.points[3]).z))\n",
    "    for face in zsorted:\n",
    "        drawFace(face, perspective, rotation, pivot, translation, scale)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "9cd4089c-0d5d-422e-802d-fc3cbeff6c59",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def ditherImage(sourceimage, ditherbands):\n",
    "    imagesplit = Image.Image.split(sourceimage)\n",
    "    imagebands = [None, None, None]\n",
    "    for c in range(3):\n",
    "        imagebands[c] = ImageMath.eval(\"convert((a>=b)*255, 'L')\", a = imagesplit[c], b = ditherbands[c])\n",
    "    return Image.merge('RGB', imagebands)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "61c21592-61f4-4878-b8f4-e0688ed64169",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def shuffleLines(image):\n",
    "    imagecopy = image.copy()\n",
    "    start = int(fixedRandoms[0])\n",
    "    maxend = int(start+fixedRandoms[1])\n",
    "    end = random.randrange(\n",
    "        int(maxend*0.75),\n",
    "        maxend\n",
    "    )\n",
    "    reduction = fixedRandoms[2]/2\n",
    "    for y in range(start, end):\n",
    "        shift = int(\n",
    "            fixedShift[(y*9)%hi]*reduction/5 +\n",
    "            abs(math.sin(y/8)*math.sin(y/22))*1.5 *\n",
    "            max(int(reduction), 0) +\n",
    "            2\n",
    "        )\n",
    "        reduction = reduction - (reduction * (1/fixedRandoms[2]))*0.75;\n",
    "        for x in range(wi):\n",
    "            pixel = image.getpixel(((x+shift)%wi,y%hi))\n",
    "            imagecopy.putpixel((x,y%hi), pixel)\n",
    "    return imagecopy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "707cbf12-e003-448e-bc5a-a87db5d9a0e0",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def fuzz(image, shifted, mask):\n",
    "    return Image.composite(image, shifted, mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "2222a115-c0f9-415b-a3da-574112c73b9e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# easing functions from https://gist.github.com/th0ma5w/9883420\n",
    "\n",
    "linearTween = lambda t, b, c, d : c*t/d + b\n",
    "\n",
    "def easeInOutQuad(frame, start, change, duration):\n",
    "    frame /= duration/2\n",
    "    if frame < 1:\n",
    "        return change/2*frame*frame + start\n",
    "    frame-=1\n",
    "    return -change/2 * (frame*(frame-2) - 1) + start"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "21cf5c28-d738-4548-96e5-7fd2ada88d93",
   "metadata": {},
   "outputs": [],
   "source": [
    "dither = Image.open(r'dithertest3.png')\n",
    "wd, hd = dither.size\n",
    "wi = 150\n",
    "hi = 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "7d59e3a8-f09c-49c3-a92a-9d619b43681a",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "random.seed(7463) #12 #255 #76543\n",
    "fixedShuffleOrder = random.sample(range(hi), hi)\n",
    "fixedShift = list(range(hi))\n",
    "for rand in range(hi):\n",
    "    fixedShift[rand] = random.randrange(6)\n",
    "fixedStripe = 4\n",
    "fixedRandoms = [150, 15, 6]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "052d8bb9-5a28-474b-adac-85850fd09e62",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def postprocessing(sourceimage, cleanimage, ditherbig, fuzzmask, fuzzmask2):\n",
    "    ditherbig = ImageChops.offset(ditherbig, int(wi*random.random()), int(hi*random.random()))\n",
    "    dithersplit = Image.Image.split(ditherbig)\n",
    "    outimage = sourceimage\n",
    "    for n in range(6):\n",
    "        outimage = ditherImage(outimage, dithersplit)\n",
    "        outimage = Image.blend(cleanimage, outimage, 0.15) #0.3\n",
    "\n",
    "        tempimage = outimage.copy()\n",
    "        fuzzmask = ImageChops.offset(fuzzmask, 0, random.randrange(hi))\n",
    "        tempimage = ImageChops.offset(tempimage, -1, 0)\n",
    "        outimage = fuzz(outimage, tempimage, fuzzmask)\n",
    "        if random.random() < 0.25:\n",
    "            fuzzmask2 = ImageChops.offset(fuzzmask2, 0, random.randrange(hi))\n",
    "            #tempimage = ImageChops.offset(tempimage, -1, 0)\n",
    "            outimage = fuzz(outimage, tempimage, fuzzmask2)\n",
    "\n",
    "        outimage = shuffleLines(outimage)\n",
    "\n",
    "    return outimage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "e160bf7f-41b8-4869-9dde-ca4df89a5a54",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 282660 characters]\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "ditherbig = Image.new('RGB', (wi,hi))\n",
    "for x in range(0, wi, wd):\n",
    "    for y in range(0, hi, hd):\n",
    "        ditherbig.paste(dither, (x, y))\n",
    "for y in range(hi):\n",
    "    for x in range(wi):\n",
    "        pixel = ditherbig.getpixel((x,y))\n",
    "        ditherbig.putpixel(\n",
    "            (\n",
    "                (x+y*fixedShift[y]*2)%wi,\n",
    "                fixedShuffleOrder[y]%hi\n",
    "            ),\n",
    "            pixel\n",
    "        )\n",
    "    pixel = ditherbig.getpixel((x,y))\n",
    "    ditherbig.putpixel(\n",
    "        (\n",
    "            x,\n",
    "            (y+random.randrange(hd))%hi\n",
    "        ),\n",
    "        pixel\n",
    "    )\n",
    "\n",
    "fuzzmask = Image.new('L', (wi, hi), 255)\n",
    "draw = ImageDraw.Draw(fuzzmask, 'L')\n",
    "for y in range(hi):\n",
    "    if fixedShift[y]==1:\n",
    "        draw.line([(0, y), (wi, y)], fill = 'black', width = 0)\n",
    "fuzzmask2 = Image.new('L', (wi, hi), 255)\n",
    "draw = ImageDraw.Draw(fuzzmask2, 'L')\n",
    "for y in range(hi):\n",
    "    if fixedShift[y]==2:\n",
    "        draw.line([(0, y), (wi, y)], fill = 'black', width = 0)\n",
    "\n",
    "frames = []\n",
    "\n",
    "filename = \"out/lf-small.gif\"\n",
    "\n",
    "frameRange = (0,35)\n",
    "duration = frameRange[1]-frameRange[0]\n",
    "#linger = (0,0) # how long to linger on the first and last frame\n",
    "\n",
    "for frame in range(0,duration):\n",
    "    image = Image.new('RGB', (wi, hi), (255,255,255,0))\n",
    "    draw = ImageDraw.Draw(image, 'RGBA')\n",
    "    #eased = easeInOutQuad(frame, 0, math.pi, duration)\n",
    "    eased = 0 #easeInOutQuad(frame, 0, 1, duration)\n",
    "    perspective = Perspective(Point2d(wi/2,hi/2), 5000)\n",
    "    #rotation = Rotation([1, 1, -0.005 * eased], 0.01 * eased);\n",
    "    rotation = Rotation([0, 1, 0], eased*2*math.pi);\n",
    "    scalefac = 0.4\n",
    "    fixedRandoms[0] = (56+eased*(hi*4))%hi + random.randrange(5)\n",
    "    drawObject(letterL + letterF, perspective, rotation, Point3d(150, 160, 0), Point3d(74, 76, 0), Point3d(1*scalefac, 1*scalefac, 0.5*scalefac))\n",
    "    \n",
    "    cleanimage = image.copy()\n",
    "    \n",
    "    image = postprocessing(image, cleanimage, ditherbig, fuzzmask, fuzzmask2)\n",
    "    image = image.resize((wi*2,hi*2), Image.Resampling.NEAREST)\n",
    "    frames.append(image)\n",
    "    \n",
    "    #if frame == 0:\n",
    "    #    for n in range(linger[0]-1):\n",
    "    #        image = postprocessing(image, cleanimage, ditherbig, fuzzmask, fuzzmask2)\n",
    "    #        image = image.resize((wi*2,hi*2), Image.Resampling.NEAREST)\n",
    "    #        frames.append(image)\n",
    "    #elif frame == duration-1:\n",
    "    #    for n in range(linger[1]-1):\n",
    "    #        image = postprocessing(image, cleanimage, ditherbig, fuzzmask, fuzzmask2)\n",
    "    #        image = image.resize((wi*2,hi*2), Image.Resampling.NEAREST)\n",
    "    #        frames.append(image)\n",
    "        \n",
    "    clear_output(wait=True)\n",
    "    print(\"Frame \" + str(frame))\n",
    "    display(image)\n",
    "    #image.save('letter-cubes2' + str(frame) + '.png')\n",
    "\n",
    "clear_output(wait=True)\n",
    "frames[0].save(filename, format='GIF', append_images=frames[1:], save_all=True, duration=60, loop=0)\n",
    "with open(filename,'rb') as file:\n",
    "    display(IPImage(file.read()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4eb9a257-e683-41f0-a9a7-1009d2cee45e",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
