Today is Tomorrow's Heritage - An Art Project I put together for a history symposium

in Art Talk2 months ago

I got an invitation to create some Art works that will be on display at a history symposium. I am electing to use this as an opportunity to return to moving images for the first time since my university Visual Art studies. (That was over 10 years ago now!)

You are all special. You get to see it almost a month in advance. Nothing will change from this, but I am pleased to share it with you. I took many of these photographs as part of the post I made recently in The Flame community, which was titled Future History.

I followed the architecture and the crumbling roadways, the growing pot holes, and the neglected footpaths.

And this is what I came back with. If the images fail to load (because they are large!) You can view the project website here.

20250814_113943_decay.gif

The area I have moved to is full of historical buildings, and there's a range of different styles, stones, and architectural features. What I am interested in is the notion of decay, and what becomes historical or heritage, beyond the virtue of simply it being what has remained.

20250814_113943.jpg
My original photograph, used for the video above</sup

However, I am not in a mood to rapidly accelerate the ageing of paint. I am a child of the digital era. As a result, I am capable of rapidly ageing images, and that is my intent, to bring awareness to the fact that preserving a structure, a wall, a public square, or whatever piece gets the label "heritage" - is not enough.

We must also be custodians of the digital artefacts of the physical artefact. While digital things will not progressively decay - they will either work, or won't, the way in which we treat those digital artefacts has a very real impact on their ability to preserve the corporeal world at a moment in time.

The same can be said of physical artefacts of physical objects and paintings. We must take care to preserve it all as best as we can, but ultimately, without the artefact being copied and preserved periodically, it will all be lost to the onslaught of time.

My pieces that I want to present are a rapid ageing. From a starting image, I have saved it at a slightly lower quality. I have then repeated this task, swapping between file formats, and each time, saving the image with a slightly lower and lower quality.

As this process continues, the colour begins to shift. From the original colour image, we see the different primary colours of the digital spectrum, red blue and green shift in unpredictable ways, as digital and compression artefacts become more and more prominent. Then, like stars in the night sky, pixels start to blink off, and eventually, there is nothing left but the black.

The spectator will left with a message, some text, then the next image will load. This work will be presented in the Town of Gawler, a QR code to allow navigation to the work itself. Witnessed in palm, on a personal device, I think it will have a bigger impact than being viewed on a communal screen.

I automated the creation of this work, after a proof of concept. it is really amazing what you can do with Python and a rudimentary understanding of the construction of PNG, WEBP, JPEG and other digital image files and how they are constructed.

If you want to reproduce this process on any of your own images, here is the code, in whole:

import random
import numpy as np
from PIL import Image, ImageDraw
import imageio.v3 as iio

input_dir = "input"
output_dir = "output"
storyboard_dir = "storyboards"
frame_count = 450                    # 15s @ 30fps
fade_duration = 120                  
frame_rate = 30
jpeg_quality = 50
temp_file = "temp_frame"
formats = ["JPEG", "WEBP", "PNG"]

# --- Prepare output dirs ---
os.makedirs(output_dir, exist_ok=True)
os.makedirs(storyboard_dir, exist_ok=True)


def horizontal_banding(frame, progress):
    frame = frame.copy()
    h, w, _ = frame.shape
    band_thickness = random.randint(1, 4)
    band_count = int(progress * 20)
    for _ in range(band_count):
        y = random.randint(0, h - band_thickness)
        frame[y:y + band_thickness, :] = np.roll(frame[y:y + band_thickness, :], random.randint(-10, 10), axis=1)
    return frame

def channel_desync(prev_frames, current_frame, i):
    """Offset R channel from earlier frame"""
    if i < 3 or len(prev_frames) < 3:
        return current_frame

    lag_frame = prev_frames[-3]  # 3 frames behind
    new = current_frame.copy()
    new[..., 0] = lag_frame[..., 0]  # replace R
    return new

def temporal_burn_in(prev_frames, current_frame, strength=0.2):
    if len(prev_frames) < 2:
        return current_frame
    echo = prev_frames[-2]
    return np.clip((1 - strength) * current_frame + strength * echo, 0, 255).astype(np.uint8)

def fade_to_black(frame, index, total):
    h, w, _ = frame.shape
    turn_off_ratio = index / total
    num_pixels = int(h * w * turn_off_ratio)
    mask = np.zeros((h * w,), dtype=bool)
    mask[np.random.choice(h * w, num_pixels, replace=False)] = True
    mask = mask.reshape((h, w))
    frame[mask] = [0, 0, 0]
    return frame

def process_image(input_path, output_path, storyboard_path):
    print(f"\nProcessing {os.path.basename(input_path)}...")

    frames = []
    prev_frames = []

    img = Image.open(input_path).convert("RGB")
    img = img.copy()

    for i in range(frame_count):
        print(f"  Frame {i + 1} of {frame_count}")

        fmt = random.choice(formats)
        temp_path = f"{temp_file}.{fmt.lower()}"

        # Save with lossy compression
        if fmt in ["JPEG", "WEBP"]:
            img.save(temp_path, fmt, quality=jpeg_quality)
        else:
            img.save(temp_path, fmt)

        img = Image.open(temp_path).convert("RGB")
        frame_np = np.array(img)

        progress = i / frame_count

        # Effects
        frame_np = horizontal_banding(frame_np, progress)
        frame_np = channel_desync(prev_frames, frame_np, i)
        frame_np = temporal_burn_in(prev_frames, frame_np)

        if i >= frame_count - fade_duration:
            frame_np = fade_to_black(frame_np, i - (frame_count - fade_duration), fade_duration)

        frames.append(frame_np)
        prev_frames.append(frame_np)

        if len(prev_frames) > 10:
            prev_frames.pop(0)

        img = Image.fromarray(frame_np)

    print("  Writing video...")
    iio.imwrite(output_path, frames, fps=frame_rate, codec='libx264', quality=8)
    print(f"  Saved video: {output_path}")

    generate_storyboard(frames, storyboard_path)

    if os.path.exists(temp_path):
        os.remove(temp_path)

def generate_storyboard(frames, output_path, columns=30):
    total_frames = len(frames)
    rows = int(np.ceil(total_frames / columns))
    h, w, _ = frames[0].shape

    # A3 landscape: 3508 x 2480 px at 300dpi (scale to match frame aspect)
    target_width = 3508
    thumb_width = target_width // columns
    thumb_height = int(h * (thumb_width / w))
    canvas = Image.new("RGB", (target_width, thumb_height * rows), (0, 0, 0))

    for idx, frame in enumerate(frames):
        row, col = divmod(idx, columns)
        thumb = Image.fromarray(frame).resize((thumb_width, thumb_height), Image.LANCZOS)
        canvas.paste(thumb, (col * thumb_width, row * thumb_height))

    canvas.save(output_path, "JPEG", quality=95)
    print(f"  Saved storyboard: {output_path}")

input_files = [f for f in os.listdir(input_dir) if f.lower().endswith(".jpg")]

if not input_files:
    print("No .jpg files found in input directory.")
else:
    for filename in input_files:
        input_path = os.path.join(input_dir, filename)
        base = os.path.splitext(filename)[0]
        output_video = os.path.join(output_dir, f"{base}_decay.mp4")
        output_storyboard = os.path.join(storyboard_dir, f"{base}_storyboard.jpg")
        process_image(input_path, output_video, output_storyboard)

Again, check out the completed work here.

I will not be renewing the domain, so when it expires, the work expires. Approximately the 11th of December, 2025. If you're seeing this after that, bad luck, this text is all you get, and you can try to reproduce it. :)


The photographs I transformed in this process were placed in the directory "input", and as long as you have python, and the required libraries installed, you can run the same script to transform your images. Just be aware, the output files will be LARGE.

As the code is non-deterministic (with calls to random) you will not get the same results that I did.

20250814_112350.jpg

20250814_113921.jpg

20250814_114105.jpg

20250814_113315.jpg

20250814_114300.jpg

20250814_115119.jpg

20250814_113122.jpg

20250814_113236.jpg

20250814_132824.jpg

Sort:  

Destruction is so much easier than creation. Choose your fighter. One dickhead with a torch, in the library of Alexandria; or the combined lifetime efforts of thousands of devoted scholars?
Destruction also has nature in it's corner. All creation has is human agency, and too much of that is devoted to destruction.

Not only that, destruction happens at an accelerated rate. It takes me some time to write a page of words. It takes the flame mere moments to erase it. So why do we fight inevitability?

Hey congrats on the exhibit and how special is it you can view it on hive in advance? The code makes my head spin a little and I am glad there are smarter people than me handling that side of things.

Love the shots and it makes me want to try the concept out on some of our historic buildings.

I do hope I haven't missed the thematic mark of the "show" entirely, there will be some other talented artists exhibiting alongside, and my "work" (in the physical space) will be a mere QR code alongside the other highly accomplished pieces.

I guess it is okay for it to get "lost", makes the work more powerful :)

I love the idea of juxtaposing digital decay over historical buildings to raise awareness about the wear and tear of time... It adds a sense of urgency and even a kind of sense of guilt, that these places are disappearing before us.

Very good thesis, and congrats on being part of the exhibit.

The best part about being in this exhibition is being asked to put something in, and not needing to justify my existence! :D

What would you recommend the best img format ?? Would you ever use svg for example ?

SVG is for vectors - logos, text, clip art, simple illustrations, not for sacred photographic images!

I think the best image format is something printed on paper, held in your hands. That way, you can rotate the image if it isn't the right way up - and just in case its a simulation on the screen, they won't be shaken out of the image, because they are forever imprinted (or stained) upon the thin slice of dead tree.

I’m gonna pick your brains more around this mate. Although you can’t beat the dead tree method . Fuck the planet .

PICK IT LIKE A FLOWER

untitled.gif