Python Challenge - Level 22

Problem

The image white.gif contains multiple frames. Inspecting the frames reveals that each frame is an empty black background with a single bright pixel positioned near (100, 100).

The coordinate displacement (Δx, Δy) = (x - 100, y - 100) mimics a joystick movement. When the displacement is (0, 0), it indicates a pen-lift / move to the next letter position.

Solution

Track the cumulative path traced by the bright pixel:

  • Compute (Δx, Δy) = (x - 100, y - 100).
  • When Δx = 0 and Δy = 0, move the drawing cursor forward (e.g. +50 px to the right) to begin a new character.
  • Draw lines or points along the cursor positions.

The reconstructed strokes spell out the word bonus.

Python

from PIL import Image, ImageDraw

img = Image.open("white.gif")
canvas = Image.new("RGB", (500, 200), (0, 0, 0))
draw = ImageDraw.Draw(canvas)

cx, cy = 0, 100

for frame in range(img.n_frames):
    img.seek(frame)
    # Get bounding box of the non-zero pixel
    bbox = img.getbbox()
    if not bbox:
        continue
    x, y = bbox[0], bbox[1]

    dx = x - 100
    dy = y - 100

    if dx == 0 and dy == 0:
        cx += 45
        cy = 100

    cx += dx
    cy += dy
    draw.point((cx, cy), fill=(255, 255, 255))

canvas.save("bonus.png")

Go

package main

import (
	"image"
	"image/color"
	"image/gif"
	"image/png"
	"os"
)

func main() {
	f, err := os.Open("white.gif")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	g, err := gif.DecodeAll(f)
	if err != nil {
		panic(err)
	}

	canvas := image.NewRGBA(image.Rect(0, 0, 500, 200))
	cx, cy := 0, 100

	for _, frame := range g.Image {
		// Find non-zero pixel in frame
		var px, py int
		found := false
		bounds := frame.Bounds()
		for y := bounds.Min.Y; y < bounds.Max.Y && !found; y++ {
			for x := bounds.Min.X; x < bounds.Max.X; x++ {
				if r, g, b, _ := frame.At(x, y).RGBA(); r > 0 || g > 0 || b > 0 {
					px, py = x, y
					found = true
					break
				}
			}
		}

		if !found {
			continue
		}

		dx := px - 100
		dy := py - 100

		if dx == 0 && dy == 0 {
			cx += 45
			cy = 100
		}

		cx += dx
		cy += dy

		if cx >= 0 && cx < 500 && cy >= 0 && cy < 200 {
			canvas.Set(cx, cy, color.White)
		}
	}

	out, _ := os.Create("bonus.png")
	defer out.Close()
	png.Encode(out, canvas)
}

Rust

use image::codecs::gif::GifDecoder;
use image::{AnimationDecoder, ImageBuffer, Rgb, RgbImage};
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open("white.gif")?;
    let decoder = GifDecoder::new(file)?;
    let frames = decoder.into_frames().collect_frames()?;

    let mut canvas: RgbImage = ImageBuffer::new(500, 200);
    let (mut cx, mut cy) = (0i32, 100i32);

    for frame in frames {
        let buffer = frame.buffer();
        let mut pt = None;

        for (x, y, pixel) in buffer.enumerate_pixels() {
            if pixel[0] > 0 || pixel[1] > 0 || pixel[2] > 0 {
                pt = Some((x as i32, y as i32));
                break;
            }
        }

        if let Some((px, py)) = pt {
            let dx = px - 100;
            let dy = py - 100;

            if dx == 0 && dy == 0 {
                cx += 45;
                cy = 100;
            }

            cx += dx;
            cy += dy;

            if cx >= 0 && cx < 500 && cy >= 0 && cy < 200 {
                canvas.put_pixel(cx as u32, cy as u32, Rgb([255, 255, 255]));
            }
        }
    }

    canvas.save("bonus.png")?;
    Ok(())
}

Language Comparison

Feature Python Go Rust
Multi-frame GIF Decoding img.seek(frame) iterator / index gif.DecodeAll() loads all frames into []*image.Paletted GifDecoder::new().into_frames() stream iterator
Bounding Box / Active Pixel img.getbbox() instantly finds non-zero bounding box Manual loop scanning pixels or custom bounding box function Iteration over enumerate_pixels()
Drawing Canvas ImageDraw.Draw(canvas).point() Direct canvas.Set(x, y, color.White) Direct canvas.put_pixel(x, y, pixel)

Key insight: Go's standard library gif.DecodeAll parses every frame of animated GIFs out of the box with zero external libraries. Rust's image crate provides a streaming animation decoder that yields framed buffers with accurate disposal and delay metadata.

Next Level

The letters spell bonus:

http://www.pythonchallenge.com/pc/hex/bonus.html