reproducing xkcd's self-description comic with python

original: this code:
from matplotlib import pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import matplotlib.image as mpimg

import os.path
from os.path import join

import numpy as np
import gc
import imageio

panelsize = 6,4
fontsize=26
spinesize=2
emptyimg = np.ones((panelsize[0]*300,panelsize[1]*100,4), dtype=float)

def normalize(l):
    return l / max(l) if not max(l) == 0 else l

def pctblack(img):
    return 1-img.mean()/255

## todo create context manager
def plotf(f, figsize=panelsize, nrows=1, ncols=1, xkcd=True):
    def g():
        fig, ax = plt.subplots(figsize=figsize, nrows=nrows, ncols=ncols)
        width, height = fig.get_size_inches() * fig.get_dpi()
        canvas = FigureCanvas(fig)
        f(fig, ax)
        canvas.draw()
        img = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')
        plt.close()
        gc.collect()
        return img.reshape(int(height), int(width), 3)
    if xkcd:
        with plt.xkcd(length=200):
            img = g()
    else:
        img = g()
    return img

def pie(pct):
    def f(fig, ax):
        wedges, texts = plt.pie(
            [pct, 1-pct],
            colors=['black','white'],
            wedgeprops={"edgecolor":"black",
                        'linewidth': 3, 'antialiased': True},
            startangle=-165,
            explode=[0,0])

        bbox_props = dict( fc="w", ec="k", lw=0.72)
        kw = dict(arrowprops=dict(arrowstyle="-"),
                  zorder=990, va="center",size=fontsize, weight='bold'
                  )

        labels = [f'FRACTION OF\nTHIS IMAGE\nWHICH IS {c}' for c in ['BLACK', 'WHITE']]


        for i, p in enumerate(wedges):
            if i==0:
                ang = (p.theta2 - p.theta1)/2. + p.theta1
            else:
                ang = 150
            y = np.sin(np.deg2rad(ang))
            x = np.cos(np.deg2rad(ang))
            connectionstyle = "angle,angleA=0,angleB={}".format(ang)
            kw["arrowprops"].update({"connectionstyle": connectionstyle})
            plt.annotate(labels[i], xy=(x,y),
                         xytext=(2.8*np.sign(x), 2*y), **kw)
        plt.tight_layout()
        fig.subplots_adjust(left=0.4, bottom=0.01, top=0.99)
    return plotf(f)

def bar(l):
    l = normalize(np.array(l))
    def f(fig,ax):
        plt.bar(range(1,4), l, color='black', width=.37)
        ax.set_xticks(range(1,4))
        ax.set_xticklabels(range(1,4), fontsize=fontsize)
        ax.set_yticks([], [])
        ax.set_xlim(0,4)
        ax.set_ylim(0,max(max(l)*1.2,0.1))
        ax.set_title('AMOUNT OF\nBLACK INK\nBY PANEL:',
                     loc='left', fontdict={'fontsize': fontsize, 'fontweight': 'bold'})
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        [ax.spines[e].set_linewidth(spinesize) for e in ["bottom","left"]]
        plt.tight_layout()
        fig.subplots_adjust(bottom=0.15, left=0.1,
                            right=0.9, top=0.7)
    return plotf(f)


def location(img):
    border = 100
    def f(fig,ax):
        plt.imshow(np.flip(img, axis=0))
        xmin, xmax = -border, img.shape[1]+border
        ymin, ymax = -border, img.shape[0]+border

        ax.set_xlim(xmin,xmax+1)
        ax.set_ylim(ymin,ymax+1)
        ax.set_xticks([0])
        ax.set_xticklabels([0], fontsize=20)
        ax.set_yticks([0])
        ax.set_yticklabels([0], fontsize=20)
        ax.set_title('LOCATION OF\nBLACK INK IN\nTHIS IMAGE:', loc='left', fontdict={'fontsize': fontsize, 'fontweight': 'bold'})
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        [ax.spines[e].set_linewidth(spinesize) for e in ["bottom","left"]]
        plt.tight_layout()
    return plotf(f)



if __name__ == '__main__':
    images = []
    for k in range(25):
        if len(images):
            img = images[-1]
            pct = pctblack(img)

            print(f' {k}: {pct*100:.4f}%', end='\r')

            if (len(images) > 1 and (abs(pctblack(images[-2]) - pct) < 0.00000001)
                or len(images) > 2 and (pctblack(images[-3]) == pct)):
                break
        else:
            pct = 0
            img = emptyimg

        indices = ((np.array(range(2))+1) * img.shape[1] / 3).astype(int)
        ll = np.split(img, indices, 1)
        imgbar = bar([pctblack(e) for e in ll])
        imgpie = pie(pct)    
        imgloc = location(img)
        def f(fig, ax):
            def plotpanel(i, img):
                ax[i].imshow(img)
                ax[i].set_xticks([])
                ax[i].set_yticks([])
                [i.set_linewidth(spinesize) for i in ax[i].spines.values()]

            for i, img in enumerate([imgpie,imgbar,imgloc]):
                plotpanel(i, img)

            plt.tight_layout()
        images.append(plotf(f, xkcd=False, ncols=3, figsize=(panelsize[0]*3,panelsize[1])))

    imageio.mimsave('plot.gif', images, duration=0.8)